@oh-my-pi/pi-coding-agent 13.6.0 → 13.6.2
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 +15 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +124 -67
- package/src/config/model-registry.ts +2 -14
- package/src/internal-urls/mcp-protocol.ts +2 -2
- package/src/mcp/manager.ts +10 -7
- package/src/mcp/smithery-registry.ts +27 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.6.2] - 2026-03-03
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- Fixed LM Studio API key retrieval to use configured provider name instead of hardcoded 'lm-studio'
|
|
9
|
+
- Fixed resource content handling to properly check for empty text values (null/undefined)
|
|
10
|
+
- Fixed resource refresh tracking to prevent stale promise reuse when server connection changes
|
|
11
|
+
- Fixed update target resolution to properly handle cases where binary path cannot be resolved
|
|
12
|
+
|
|
13
|
+
## [13.6.1] - 2026-03-03
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed `omp update` silently succeeding without actually updating the binary when the update channel (bun global vs compiled binary) doesn't match the installation method ([#247](https://github.com/can1357/oh-my-pi/issues/247))
|
|
18
|
+
- Added post-update verification that checks the resolved `omp` binary reports the expected version, with actionable warnings on mismatch
|
|
19
|
+
- `omp update` now detects when the `omp` in PATH is not managed by bun and falls back to binary replacement instead of updating the wrong location
|
|
5
20
|
## [13.6.0] - 2026-03-03
|
|
6
21
|
### Added
|
|
7
22
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.6.
|
|
4
|
+
"version": "13.6.2",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.6.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.6.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.6.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.6.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.6.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.6.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.6.2",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.6.2",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.6.2",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.6.2",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.6.2",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.6.2",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -4,29 +4,20 @@
|
|
|
4
4
|
* Handles `omp update` to check for and install updates.
|
|
5
5
|
* Uses bun if available, otherwise downloads binary from GitHub releases.
|
|
6
6
|
*/
|
|
7
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
8
7
|
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
9
|
import { pipeline } from "node:stream/promises";
|
|
10
10
|
import { APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { $ } from "bun";
|
|
11
12
|
import chalk from "chalk";
|
|
12
13
|
import { theme } from "../modes/theme/theme";
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* Detect if we're running as a Bun compiled binary.
|
|
16
|
-
*/
|
|
17
|
-
const isBunBinary =
|
|
18
|
-
Bun.env.PI_COMPILED ||
|
|
19
|
-
import.meta.url.includes("$bunfs") ||
|
|
20
|
-
import.meta.url.includes("~BUN") ||
|
|
21
|
-
import.meta.url.includes("%7EBUN");
|
|
22
|
-
|
|
23
15
|
const REPO = "can1357/oh-my-pi";
|
|
24
16
|
const PACKAGE = "@oh-my-pi/pi-coding-agent";
|
|
25
17
|
|
|
26
18
|
interface ReleaseInfo {
|
|
27
19
|
tag: string;
|
|
28
20
|
version: string;
|
|
29
|
-
assets: Array<{ name: string; url: string }>;
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
/**
|
|
@@ -44,16 +35,54 @@ export function parseUpdateArgs(args: string[]): { force: boolean; check: boolea
|
|
|
44
35
|
};
|
|
45
36
|
}
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
function hasBun(): boolean {
|
|
38
|
+
async function getBunGlobalBinDir(): Promise<string | undefined> {
|
|
39
|
+
if (!Bun.which("bun")) return undefined;
|
|
51
40
|
try {
|
|
52
|
-
const result =
|
|
53
|
-
|
|
41
|
+
const result = await $`bun pm bin -g`.quiet().nothrow();
|
|
42
|
+
if (result.exitCode !== 0) return undefined;
|
|
43
|
+
const output = result.text().trim();
|
|
44
|
+
return output.length > 0 ? output : undefined;
|
|
54
45
|
} catch {
|
|
55
|
-
return
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizePathForComparison(filePath: string): string {
|
|
51
|
+
const normalized = path.normalize(filePath);
|
|
52
|
+
if (process.platform === "win32") return normalized.toLowerCase();
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
57
|
+
const normalizedPath = normalizePathForComparison(path.resolve(filePath));
|
|
58
|
+
const normalizedDirectory = normalizePathForComparison(path.resolve(directoryPath));
|
|
59
|
+
const relativePath = path.relative(normalizedDirectory, normalizedPath);
|
|
60
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type UpdateTarget = { method: "bun" } | { method: "binary"; path: string };
|
|
64
|
+
|
|
65
|
+
function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
66
|
+
if (!bunBinDir) return "binary";
|
|
67
|
+
return isPathInDirectory(ompPath, bunBinDir) ? "bun" : "binary";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function _resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
71
|
+
return resolveUpdateMethod(ompPath, bunBinDir);
|
|
72
|
+
}
|
|
73
|
+
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
74
|
+
const bunBinDir = await getBunGlobalBinDir();
|
|
75
|
+
const ompPath = resolveOmpPath();
|
|
76
|
+
|
|
77
|
+
if (ompPath) {
|
|
78
|
+
const method = resolveUpdateMethod(ompPath, bunBinDir);
|
|
79
|
+
if (method === "bun") return { method };
|
|
80
|
+
return { method, path: ompPath };
|
|
56
81
|
}
|
|
82
|
+
|
|
83
|
+
if (bunBinDir) return { method: "bun" };
|
|
84
|
+
|
|
85
|
+
throw new Error(`Could not resolve ${APP_NAME} binary path in PATH`);
|
|
57
86
|
}
|
|
58
87
|
|
|
59
88
|
/**
|
|
@@ -70,15 +99,9 @@ async function getLatestRelease(): Promise<ReleaseInfo> {
|
|
|
70
99
|
const version = data.version;
|
|
71
100
|
const tag = `v${version}`;
|
|
72
101
|
|
|
73
|
-
// Construct deterministic GitHub release download URLs for the current platform
|
|
74
|
-
const makeAsset = (name: string) => ({
|
|
75
|
-
name,
|
|
76
|
-
url: `https://github.com/${REPO}/releases/download/${tag}/${name}`,
|
|
77
|
-
});
|
|
78
102
|
return {
|
|
79
103
|
tag,
|
|
80
104
|
version,
|
|
81
|
-
assets: [makeAsset(getBinaryName())],
|
|
82
105
|
};
|
|
83
106
|
}
|
|
84
107
|
|
|
@@ -140,60 +163,93 @@ function getBinaryName(): string {
|
|
|
140
163
|
return `${APP_NAME}-${os}-${archName}`;
|
|
141
164
|
}
|
|
142
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Resolve the path that `omp` maps to in the user's PATH.
|
|
168
|
+
*/
|
|
169
|
+
function resolveOmpPath(): string | undefined {
|
|
170
|
+
return Bun.which(APP_NAME) ?? undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Run the resolved omp binary and check if it reports the expected version.
|
|
175
|
+
*/
|
|
176
|
+
async function verifyInstalledVersion(
|
|
177
|
+
expectedVersion: string,
|
|
178
|
+
): Promise<{ ok: boolean; actual?: string; path?: string }> {
|
|
179
|
+
const ompPath = resolveOmpPath();
|
|
180
|
+
if (!ompPath) return { ok: false };
|
|
181
|
+
try {
|
|
182
|
+
const result = await $`${ompPath} --version`.quiet().nothrow();
|
|
183
|
+
if (result.exitCode !== 0) return { ok: false, path: ompPath };
|
|
184
|
+
const output = result.text().trim();
|
|
185
|
+
// Output format: "omp/X.Y.Z"
|
|
186
|
+
const match = output.match(/\/(\d+\.\d+\.\d+)/);
|
|
187
|
+
const actual = match?.[1];
|
|
188
|
+
return { ok: actual === expectedVersion, actual, path: ompPath };
|
|
189
|
+
} catch {
|
|
190
|
+
return { ok: false, path: ompPath };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Print post-update verification result.
|
|
196
|
+
*/
|
|
197
|
+
async function printVerification(expectedVersion: string): Promise<void> {
|
|
198
|
+
const result = await verifyInstalledVersion(expectedVersion);
|
|
199
|
+
if (result.ok) {
|
|
200
|
+
console.log(chalk.green(`\n${theme.status.success} Updated to ${expectedVersion}`));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (result.actual) {
|
|
204
|
+
console.log(
|
|
205
|
+
chalk.yellow(
|
|
206
|
+
`\nWarning: ${APP_NAME} at ${result.path} still reports ${result.actual} (expected ${expectedVersion})`,
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
} else {
|
|
210
|
+
console.log(
|
|
211
|
+
chalk.yellow(`\nWarning: could not verify updated version${result.path ? ` at ${result.path}` : ""}`),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
console.log(
|
|
215
|
+
chalk.yellow(
|
|
216
|
+
`You may need to reinstall: curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | bash`,
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
143
221
|
/**
|
|
144
222
|
* Update via bun package manager.
|
|
145
223
|
*/
|
|
146
224
|
async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
147
225
|
console.log(chalk.dim("Updating via bun..."));
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
throw new Error("bun install failed", { cause: error });
|
|
226
|
+
const result = await $`bun install -g ${PACKAGE}@${expectedVersion}`.nothrow();
|
|
227
|
+
if (result.exitCode !== 0) {
|
|
228
|
+
throw new Error(`bun install failed with exit code ${result.exitCode}`);
|
|
152
229
|
}
|
|
153
230
|
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const result = spawnSync("bun", ["pm", "ls", "-g"], { encoding: "utf-8", stdio: "pipe" });
|
|
157
|
-
const output = result.stdout || "";
|
|
158
|
-
const match = output.match(new RegExp(`${PACKAGE.replace("/", "\\/")}@(\\S+)`));
|
|
159
|
-
if (match) {
|
|
160
|
-
const installedVersion = match[1];
|
|
161
|
-
if (compareVersions(installedVersion, expectedVersion) < 0) {
|
|
162
|
-
console.log(
|
|
163
|
-
chalk.yellow(`\nWarning: bun reports ${installedVersion} installed, expected ${expectedVersion}`),
|
|
164
|
-
);
|
|
165
|
-
console.log(chalk.yellow(`Try: bun install -g ${PACKAGE}@latest`));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} catch {
|
|
170
|
-
// Verification is best-effort, don't fail the update
|
|
171
|
-
}
|
|
172
|
-
console.log(chalk.green(`\n${theme.status.success} Update complete`));
|
|
231
|
+
await printVerification(expectedVersion);
|
|
173
232
|
}
|
|
174
233
|
|
|
175
234
|
/**
|
|
176
|
-
*
|
|
235
|
+
* Download a release binary to a target path, replacing an existing file.
|
|
177
236
|
*/
|
|
178
|
-
async function
|
|
237
|
+
async function updateViaBinaryAt(targetPath: string, expectedVersion: string): Promise<void> {
|
|
179
238
|
const binaryName = getBinaryName();
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
const
|
|
185
|
-
const tempPath = `${execPath}.new`;
|
|
186
|
-
const backupPath = `${execPath}.bak`;
|
|
239
|
+
const tag = `v${expectedVersion}`;
|
|
240
|
+
const url = `https://github.com/${REPO}/releases/download/${tag}/${binaryName}`;
|
|
241
|
+
|
|
242
|
+
const tempPath = `${targetPath}.new`;
|
|
243
|
+
const backupPath = `${targetPath}.bak`;
|
|
187
244
|
console.log(chalk.dim(`Downloading ${binaryName}…`));
|
|
188
245
|
|
|
189
|
-
|
|
190
|
-
const response = await fetch(asset.url, { redirect: "follow" });
|
|
246
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
191
247
|
if (!response.ok || !response.body) {
|
|
192
248
|
throw new Error(`Download failed: ${response.statusText}`);
|
|
193
249
|
}
|
|
194
250
|
const fileStream = fs.createWriteStream(tempPath, { mode: 0o755 });
|
|
195
251
|
await pipeline(response.body, fileStream);
|
|
196
|
-
|
|
252
|
+
|
|
197
253
|
console.log(chalk.dim("Installing update..."));
|
|
198
254
|
try {
|
|
199
255
|
try {
|
|
@@ -201,15 +257,15 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
|
201
257
|
} catch (err) {
|
|
202
258
|
if (!isEnoent(err)) throw err;
|
|
203
259
|
}
|
|
204
|
-
await fs.promises.rename(
|
|
205
|
-
await fs.promises.rename(tempPath,
|
|
260
|
+
await fs.promises.rename(targetPath, backupPath);
|
|
261
|
+
await fs.promises.rename(tempPath, targetPath);
|
|
206
262
|
await fs.promises.unlink(backupPath);
|
|
207
263
|
|
|
208
|
-
|
|
264
|
+
await printVerification(expectedVersion);
|
|
209
265
|
console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
|
|
210
266
|
} catch (err) {
|
|
211
|
-
if (fs.existsSync(backupPath) && !fs.existsSync(
|
|
212
|
-
await fs.promises.rename(backupPath,
|
|
267
|
+
if (fs.existsSync(backupPath) && !fs.existsSync(targetPath)) {
|
|
268
|
+
await fs.promises.rename(backupPath, targetPath);
|
|
213
269
|
}
|
|
214
270
|
if (fs.existsSync(tempPath)) {
|
|
215
271
|
await fs.promises.unlink(tempPath);
|
|
@@ -251,12 +307,13 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
251
307
|
return;
|
|
252
308
|
}
|
|
253
309
|
|
|
254
|
-
// Choose update method
|
|
310
|
+
// Choose update method based on the prioritized omp binary in PATH
|
|
255
311
|
try {
|
|
256
|
-
|
|
312
|
+
const target = await resolveUpdateTarget();
|
|
313
|
+
if (target.method === "bun") {
|
|
257
314
|
await updateViaBun(release.version);
|
|
258
315
|
} else {
|
|
259
|
-
await
|
|
316
|
+
await updateViaBinaryAt(target.path, release.version);
|
|
260
317
|
}
|
|
261
318
|
} catch (err) {
|
|
262
319
|
console.error(chalk.red(`Update failed: ${err}`));
|
|
@@ -6,13 +6,11 @@ import {
|
|
|
6
6
|
DEFAULT_LOCAL_TOKEN,
|
|
7
7
|
getBundledModels,
|
|
8
8
|
getBundledProviders,
|
|
9
|
-
getGitHubCopilotBaseUrl,
|
|
10
9
|
googleAntigravityModelManagerOptions,
|
|
11
10
|
googleGeminiCliModelManagerOptions,
|
|
12
11
|
type Model,
|
|
13
12
|
type ModelManagerOptions,
|
|
14
13
|
type ModelRefreshStrategy,
|
|
15
|
-
normalizeDomain,
|
|
16
14
|
type OAuthCredentials,
|
|
17
15
|
type OAuthLoginCallbacks,
|
|
18
16
|
openaiCodexModelManagerOptions,
|
|
@@ -539,17 +537,7 @@ export class ModelRegistry {
|
|
|
539
537
|
const builtInModels = this.#loadBuiltInModels(overrides, modelOverrides);
|
|
540
538
|
const combined = this.#mergeCustomModels(builtInModels, customModels);
|
|
541
539
|
|
|
542
|
-
|
|
543
|
-
const copilotCred = this.authStorage.getOAuthCredential("github-copilot");
|
|
544
|
-
if (copilotCred) {
|
|
545
|
-
const domain = copilotCred.enterpriseUrl
|
|
546
|
-
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
|
|
547
|
-
: undefined;
|
|
548
|
-
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
|
|
549
|
-
this.#models = combined.map(m => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
|
550
|
-
} else {
|
|
551
|
-
this.#models = combined;
|
|
552
|
-
}
|
|
540
|
+
this.#models = combined;
|
|
553
541
|
}
|
|
554
542
|
|
|
555
543
|
/** Load built-in models, applying provider and per-model overrides */
|
|
@@ -895,7 +883,7 @@ export class ModelRegistry {
|
|
|
895
883
|
const modelsUrl = `${baseUrl}/models`;
|
|
896
884
|
|
|
897
885
|
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
898
|
-
const apiKey = await this.authStorage.getApiKey(
|
|
886
|
+
const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
|
|
899
887
|
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
900
888
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
901
889
|
}
|
|
@@ -137,14 +137,14 @@ export class McpProtocolHandler implements ProtocolHandler {
|
|
|
137
137
|
|
|
138
138
|
const textParts: string[] = [];
|
|
139
139
|
for (const item of result.contents) {
|
|
140
|
-
if (item.text) {
|
|
140
|
+
if (item.text !== undefined && item.text !== null) {
|
|
141
141
|
textParts.push(item.text);
|
|
142
142
|
} else if (item.blob) {
|
|
143
143
|
textParts.push(`[Binary content: ${item.mimeType ?? "unknown"}, base64 length ${item.blob.length}]`);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
const content = textParts.join("\n---\n")
|
|
147
|
+
const content = textParts.length > 0 ? textParts.join("\n---\n") : "(empty resource)";
|
|
148
148
|
return {
|
|
149
149
|
url: url.href,
|
|
150
150
|
content,
|
package/src/mcp/manager.ts
CHANGED
|
@@ -126,7 +126,7 @@ export class MCPManager {
|
|
|
126
126
|
#notificationsEnabled = false;
|
|
127
127
|
#notificationsEpoch = 0;
|
|
128
128
|
#subscribedResources = new Map<string, Set<string>>();
|
|
129
|
-
#pendingResourceRefresh = new Map<string, Promise<void
|
|
129
|
+
#pendingResourceRefresh = new Map<string, { connection: MCPServerConnection; promise: Promise<void> }>();
|
|
130
130
|
|
|
131
131
|
constructor(
|
|
132
132
|
private cwd: string,
|
|
@@ -590,6 +590,7 @@ export class MCPManager {
|
|
|
590
590
|
this.#pendingConnections.delete(name);
|
|
591
591
|
this.#pendingToolLoads.delete(name);
|
|
592
592
|
this.#sources.delete(name);
|
|
593
|
+
this.#pendingResourceRefresh.delete(name);
|
|
593
594
|
|
|
594
595
|
const connection = this.#connections.get(name);
|
|
595
596
|
|
|
@@ -622,6 +623,7 @@ export class MCPManager {
|
|
|
622
623
|
|
|
623
624
|
this.#pendingConnections.clear();
|
|
624
625
|
this.#pendingToolLoads.clear();
|
|
626
|
+
this.#pendingResourceRefresh.clear();
|
|
625
627
|
this.#sources.clear();
|
|
626
628
|
this.#connections.clear();
|
|
627
629
|
this.#tools = [];
|
|
@@ -660,13 +662,13 @@ export class MCPManager {
|
|
|
660
662
|
* Refresh resources from a specific server.
|
|
661
663
|
*/
|
|
662
664
|
async refreshServerResources(name: string): Promise<void> {
|
|
665
|
+
const connection = this.#connections.get(name);
|
|
666
|
+
if (!connection || !serverSupportsResources(connection.capabilities)) return;
|
|
667
|
+
|
|
663
668
|
const existing = this.#pendingResourceRefresh.get(name);
|
|
664
|
-
if (existing) return existing;
|
|
669
|
+
if (existing && existing.connection === connection) return existing.promise;
|
|
665
670
|
|
|
666
671
|
const doRefresh = async (): Promise<void> => {
|
|
667
|
-
const connection = this.#connections.get(name);
|
|
668
|
-
if (!connection || !serverSupportsResources(connection.capabilities)) return;
|
|
669
|
-
|
|
670
672
|
// Clear cached resources
|
|
671
673
|
connection.resources = undefined;
|
|
672
674
|
connection.resourceTemplates = undefined;
|
|
@@ -716,11 +718,12 @@ export class MCPManager {
|
|
|
716
718
|
};
|
|
717
719
|
|
|
718
720
|
const promise = doRefresh().finally(() => {
|
|
719
|
-
|
|
721
|
+
const pending = this.#pendingResourceRefresh.get(name);
|
|
722
|
+
if (pending?.promise === promise) {
|
|
720
723
|
this.#pendingResourceRefresh.delete(name);
|
|
721
724
|
}
|
|
722
725
|
});
|
|
723
|
-
this.#pendingResourceRefresh.set(name, promise);
|
|
726
|
+
this.#pendingResourceRefresh.set(name, { connection, promise });
|
|
724
727
|
return promise;
|
|
725
728
|
}
|
|
726
729
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { MCPServerConfig } from "./types";
|
|
2
3
|
|
|
3
4
|
const SMITHERY_REGISTRY_BASE_URL = "https://registry.smithery.ai";
|
|
@@ -324,8 +325,12 @@ async function fetchServerDetailsFromEntry(
|
|
|
324
325
|
): Promise<SmitheryServerDetails | null> {
|
|
325
326
|
const candidates = resolveDetailPathCandidates(entry);
|
|
326
327
|
for (const candidate of candidates) {
|
|
327
|
-
|
|
328
|
-
|
|
328
|
+
try {
|
|
329
|
+
const details = await fetchServerDetails(candidate, options);
|
|
330
|
+
if (details) return details;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
logger.debug("Smithery detail fetch candidate failed", { candidate, error: String(error) });
|
|
333
|
+
}
|
|
329
334
|
}
|
|
330
335
|
return null;
|
|
331
336
|
}
|
|
@@ -439,14 +444,31 @@ export async function searchSmitheryRegistry(
|
|
|
439
444
|
);
|
|
440
445
|
});
|
|
441
446
|
|
|
447
|
+
const detailFailures: Array<{ identity: string; error: string }> = [];
|
|
442
448
|
const results = await Promise.all(
|
|
443
449
|
uniqueEntries.map(async entry => {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
450
|
+
try {
|
|
451
|
+
const details = await fetchServerDetailsFromEntry(entry, { apiKey: options?.apiKey });
|
|
452
|
+
if (!details) return null;
|
|
453
|
+
return toSearchResult(entry, details);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
detailFailures.push({
|
|
456
|
+
identity: getEntryIdentityKey(entry) ?? entry.id ?? "unknown",
|
|
457
|
+
error: String(error),
|
|
458
|
+
});
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
447
461
|
}),
|
|
448
462
|
);
|
|
449
463
|
|
|
464
|
+
if (detailFailures.length > 0) {
|
|
465
|
+
logger.warn("Smithery detail fetch failed for some entries", {
|
|
466
|
+
query,
|
|
467
|
+
failedEntries: detailFailures.length,
|
|
468
|
+
totalEntries: uniqueEntries.length,
|
|
469
|
+
sample: detailFailures.slice(0, 3),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
450
472
|
return results.filter((result): result is SmitherySearchResult => result !== null).slice(0, limit);
|
|
451
473
|
}
|
|
452
474
|
|