@oh-my-pi/pi-coding-agent 13.5.8 → 13.6.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/CHANGELOG.md +37 -1
- package/package.json +7 -7
- package/src/cli/args.ts +7 -0
- package/src/cli/stats-cli.ts +5 -0
- package/src/cli/update-cli.ts +127 -67
- package/src/config/model-registry.ts +100 -22
- package/src/config/settings-schema.ts +22 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/internal-urls/index.ts +2 -1
- package/src/internal-urls/mcp-protocol.ts +156 -0
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +3 -3
- package/src/mcp/client.ts +235 -2
- package/src/mcp/index.ts +1 -1
- package/src/mcp/manager.ts +399 -5
- package/src/mcp/oauth-flow.ts +26 -1
- package/src/mcp/smithery-auth.ts +104 -0
- package/src/mcp/smithery-connect.ts +145 -0
- package/src/mcp/smithery-registry.ts +455 -0
- package/src/mcp/types.ts +140 -0
- package/src/modes/components/footer.ts +10 -4
- package/src/modes/components/settings-defs.ts +15 -1
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/presets.ts +6 -6
- package/src/modes/components/status-line/segments.ts +27 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +109 -5
- package/src/modes/controllers/command-controller.ts +12 -2
- package/src/modes/controllers/extension-ui-controller.ts +12 -21
- package/src/modes/controllers/mcp-command-controller.ts +577 -14
- package/src/modes/controllers/selector-controller.ts +5 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/tools/hashline.md +4 -3
- package/src/sdk.ts +115 -3
- package/src/session/agent-session.ts +19 -4
- package/src/session/session-manager.ts +17 -5
- package/src/slash-commands/builtin-registry.ts +10 -0
- package/src/task/executor.ts +37 -3
- package/src/task/index.ts +37 -5
- package/src/task/isolation-backend.ts +72 -0
- package/src/task/render.ts +6 -1
- package/src/task/types.ts +1 -0
- package/src/task/worktree.ts +67 -5
- package/src/tools/index.ts +1 -1
- package/src/tools/path-utils.ts +2 -1
- package/src/tools/read.ts +3 -7
- package/src/utils/open.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.6.1] - 2026-03-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- 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))
|
|
10
|
+
- Added post-update verification that checks the resolved `omp` binary reports the expected version, with actionable warnings on mismatch
|
|
11
|
+
- `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
|
|
12
|
+
## [13.6.0] - 2026-03-03
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added `mcp://` internal URL protocol for reading MCP server resources directly via the read tool (e.g., `read(path="mcp://resource-uri")`)
|
|
16
|
+
- Added LM Studio integration to the model registry and discovery flow.
|
|
17
|
+
- Added support for authenticating with LM Studio using the `/login lm-studio` command.
|
|
18
|
+
- Added `fuse-projfs` task isolation mode for Windows ProjFS-backed overlays.
|
|
19
|
+
- Added `/mcp registry search <keyword>` integration with Smithery, including interactive result selection, editable server naming before deploy, Smithery `configSchema` prompts, and immediate runtime reload so selected MCP tools are available without restarting
|
|
20
|
+
- Added OAuth failure fallback in `/mcp registry search` deploy flow to prompt for manual bearer tokens and validate them before saving configuration
|
|
21
|
+
- Added Smithery auth support for `/mcp registry search` with cached API key login (`/mcp registry login`, `/mcp registry logout`) and automatic login prompt/retry on auth or rate-limit responses
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Updated MCP resource update notifications to recommend using `read(path="mcp://<uri>")` instead of the deprecated `read_resource` tool
|
|
26
|
+
- Updated Anthropic Foundry environment variable documentation and CLI help text to the canonical names: `CLAUDE_CODE_USE_FOUNDRY`, `CLAUDE_CODE_CLIENT_CERT`, and `CLAUDE_CODE_CLIENT_KEY`
|
|
27
|
+
- Documented Foundry-specific Anthropic runtime configuration (`FOUNDRY_BASE_URL`, `ANTHROPIC_FOUNDRY_API_KEY`, `ANTHROPIC_CUSTOM_HEADERS`, `NODE_EXTRA_CA_CERTS`) in environment variable reference docs
|
|
28
|
+
- `fuse-overlay` task isolation now targets `fuse-overlayfs` on Unix hosts only; on Windows it falls back to `worktree` with a `<system-notification>` suggesting `fuse-projfs`.
|
|
29
|
+
- `fuse-projfs` now performs Windows ProjFS preflight checks and falls back to `worktree` when host or repository prerequisites are unavailable.
|
|
30
|
+
- Cross-repo patch capture now uses the platform null device (`NUL` on Windows, `/dev/null` elsewhere) for `git diff --no-index`.
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
- Removed `read_resource` tool; MCP resource reading is now integrated into the `read` tool via `mcp://` URLs
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Fixed MCP resource subscription handling to prevent unsubscribing when notifications are re-enabled after being disabled
|
|
39
|
+
- Fixed LM Studio base URL validation to preserve invalid configured URLs instead of silently falling back to localhost
|
|
40
|
+
- Fixed URI template matching to correctly handle expressions that expand to empty strings
|
|
41
|
+
|
|
5
42
|
## [13.5.6] - 2026-03-01
|
|
6
43
|
### Changed
|
|
7
44
|
|
|
@@ -1051,7 +1088,6 @@
|
|
|
1051
1088
|
- Improved error reporting in fetch tool to include HTTP status codes when URL fetching fails
|
|
1052
1089
|
- Fixed fetch tool to preserve actual response metadata (finalUrl, contentType) instead of defaults when requests fail
|
|
1053
1090
|
|
|
1054
|
-
||||||| parent of a70a34c8b (fix(coding-agent/debug): Sanitized debug log rendering)
|
|
1055
1091
|
|
|
1056
1092
|
## [12.1.0] - 2026-02-13
|
|
1057
1093
|
|
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.
|
|
4
|
+
"version": "13.6.1",
|
|
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.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.6.1",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.6.1",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.6.1",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.6.1",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.6.1",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.6.1",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
package/src/cli/args.ts
CHANGED
|
@@ -191,6 +191,13 @@ export function getExtraHelpText(): string {
|
|
|
191
191
|
${chalk.dim("# Core Providers")}
|
|
192
192
|
ANTHROPIC_API_KEY - Anthropic Claude models
|
|
193
193
|
ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth (takes precedence over API key)
|
|
194
|
+
CLAUDE_CODE_USE_FOUNDRY - Enable Anthropic Foundry mode (uses Foundry endpoint + mTLS)
|
|
195
|
+
FOUNDRY_BASE_URL - Anthropic Foundry base URL (e.g., https://<foundry-host>)
|
|
196
|
+
ANTHROPIC_FOUNDRY_API_KEY - Anthropic token used as Authorization: Bearer <token> in Foundry mode
|
|
197
|
+
ANTHROPIC_CUSTOM_HEADERS - Extra Foundry headers (e.g., "user-id: USERNAME")
|
|
198
|
+
CLAUDE_CODE_CLIENT_CERT - Client certificate (PEM path or inline PEM) for mTLS
|
|
199
|
+
CLAUDE_CODE_CLIENT_KEY - Client private key (PEM path or inline PEM) for mTLS
|
|
200
|
+
NODE_EXTRA_CA_CERTS - CA bundle path (or inline PEM) for server certificate validation
|
|
194
201
|
OPENAI_API_KEY - OpenAI GPT models
|
|
195
202
|
GEMINI_API_KEY - Google Gemini models
|
|
196
203
|
GITHUB_TOKEN - GitHub Copilot (or GH_TOKEN, COPILOT_GITHUB_TOKEN)
|
package/src/cli/stats-cli.ts
CHANGED
|
@@ -59,6 +59,10 @@ function formatCost(n: number): string {
|
|
|
59
59
|
return `$${n.toFixed(2)}`;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function normalizePremiumRequests(n: number): number {
|
|
63
|
+
return Math.round((n + Number.EPSILON) * 100) / 100;
|
|
64
|
+
}
|
|
65
|
+
|
|
62
66
|
// =============================================================================
|
|
63
67
|
// Command Handler
|
|
64
68
|
// =============================================================================
|
|
@@ -120,6 +124,7 @@ async function printStatsSummary(): Promise<void> {
|
|
|
120
124
|
console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
|
|
121
125
|
console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
|
|
122
126
|
console.log(` Total Cost: ${formatCost(overall.totalCost)}`);
|
|
127
|
+
console.log(` Premium Requests: ${formatNumber(normalizePremiumRequests(overall.totalPremiumRequests ?? 0))}`);
|
|
123
128
|
console.log(` Avg Duration: ${overall.avgDuration !== null ? formatDuration(overall.avgDuration) : "-"}`);
|
|
124
129
|
console.log(` Avg TTFT: ${overall.avgTtft !== null ? formatDuration(overall.avgTtft) : "-"}`);
|
|
125
130
|
if (overall.avgTokensPerSecond !== null) {
|
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,18 +35,59 @@ 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;
|
|
56
47
|
}
|
|
57
48
|
}
|
|
58
49
|
|
|
50
|
+
function getRealPathOrOriginal(filePath: string): string {
|
|
51
|
+
try {
|
|
52
|
+
return fs.realpathSync(filePath);
|
|
53
|
+
} catch {
|
|
54
|
+
return filePath;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizePathForComparison(filePath: string): string {
|
|
59
|
+
const normalized = path.normalize(filePath);
|
|
60
|
+
if (process.platform === "win32") return normalized.toLowerCase();
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
65
|
+
const normalizedPath = normalizePathForComparison(getRealPathOrOriginal(filePath));
|
|
66
|
+
const normalizedDirectory = normalizePathForComparison(getRealPathOrOriginal(directoryPath));
|
|
67
|
+
const relativePath = path.relative(normalizedDirectory, normalizedPath);
|
|
68
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface UpdateTarget {
|
|
72
|
+
method: "bun" | "binary";
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
77
|
+
if (!bunBinDir) return "binary";
|
|
78
|
+
return isPathInDirectory(ompPath, bunBinDir) ? "bun" : "binary";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function _resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
82
|
+
return resolveUpdateMethod(ompPath, bunBinDir);
|
|
83
|
+
}
|
|
84
|
+
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
85
|
+
const ompPath = resolveOmpPath() ?? process.execPath;
|
|
86
|
+
const bunBinDir = await getBunGlobalBinDir();
|
|
87
|
+
const method = resolveUpdateMethod(ompPath, bunBinDir);
|
|
88
|
+
return { method, path: ompPath };
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
/**
|
|
60
92
|
* Get the latest release info from the npm registry.
|
|
61
93
|
* Uses npm instead of GitHub API to avoid unauthenticated rate limiting.
|
|
@@ -70,15 +102,9 @@ async function getLatestRelease(): Promise<ReleaseInfo> {
|
|
|
70
102
|
const version = data.version;
|
|
71
103
|
const tag = `v${version}`;
|
|
72
104
|
|
|
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
105
|
return {
|
|
79
106
|
tag,
|
|
80
107
|
version,
|
|
81
|
-
assets: [makeAsset(getBinaryName())],
|
|
82
108
|
};
|
|
83
109
|
}
|
|
84
110
|
|
|
@@ -140,60 +166,93 @@ function getBinaryName(): string {
|
|
|
140
166
|
return `${APP_NAME}-${os}-${archName}`;
|
|
141
167
|
}
|
|
142
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Resolve the path that `omp` maps to in the user's PATH.
|
|
171
|
+
*/
|
|
172
|
+
function resolveOmpPath(): string | undefined {
|
|
173
|
+
return Bun.which(APP_NAME) ?? undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Run the resolved omp binary and check if it reports the expected version.
|
|
178
|
+
*/
|
|
179
|
+
async function verifyInstalledVersion(
|
|
180
|
+
expectedVersion: string,
|
|
181
|
+
): Promise<{ ok: boolean; actual?: string; path?: string }> {
|
|
182
|
+
const ompPath = resolveOmpPath();
|
|
183
|
+
if (!ompPath) return { ok: false };
|
|
184
|
+
try {
|
|
185
|
+
const result = await $`${ompPath} --version`.quiet().nothrow();
|
|
186
|
+
if (result.exitCode !== 0) return { ok: false, path: ompPath };
|
|
187
|
+
const output = result.text().trim();
|
|
188
|
+
// Output format: "omp/X.Y.Z"
|
|
189
|
+
const match = output.match(/\/(\d+\.\d+\.\d+)/);
|
|
190
|
+
const actual = match?.[1];
|
|
191
|
+
return { ok: actual === expectedVersion, actual, path: ompPath };
|
|
192
|
+
} catch {
|
|
193
|
+
return { ok: false, path: ompPath };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Print post-update verification result.
|
|
199
|
+
*/
|
|
200
|
+
async function printVerification(expectedVersion: string): Promise<void> {
|
|
201
|
+
const result = await verifyInstalledVersion(expectedVersion);
|
|
202
|
+
if (result.ok) {
|
|
203
|
+
console.log(chalk.green(`\n${theme.status.success} Updated to ${expectedVersion}`));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (result.actual) {
|
|
207
|
+
console.log(
|
|
208
|
+
chalk.yellow(
|
|
209
|
+
`\nWarning: ${APP_NAME} at ${result.path} still reports ${result.actual} (expected ${expectedVersion})`,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
console.log(
|
|
214
|
+
chalk.yellow(`\nWarning: could not verify updated version${result.path ? ` at ${result.path}` : ""}`),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
console.log(
|
|
218
|
+
chalk.yellow(
|
|
219
|
+
`You may need to reinstall: curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | bash`,
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
143
224
|
/**
|
|
144
225
|
* Update via bun package manager.
|
|
145
226
|
*/
|
|
146
227
|
async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
147
228
|
console.log(chalk.dim("Updating via bun..."));
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
throw new Error("bun install failed", { cause: error });
|
|
229
|
+
const result = await $`bun install -g ${PACKAGE}@${expectedVersion}`.nothrow();
|
|
230
|
+
if (result.exitCode !== 0) {
|
|
231
|
+
throw new Error(`bun install failed with exit code ${result.exitCode}`);
|
|
152
232
|
}
|
|
153
233
|
|
|
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`));
|
|
234
|
+
await printVerification(expectedVersion);
|
|
173
235
|
}
|
|
174
236
|
|
|
175
237
|
/**
|
|
176
|
-
*
|
|
238
|
+
* Download a release binary to a target path, replacing an existing file.
|
|
177
239
|
*/
|
|
178
|
-
async function
|
|
240
|
+
async function updateViaBinaryAt(targetPath: string, expectedVersion: string): Promise<void> {
|
|
179
241
|
const binaryName = getBinaryName();
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
const
|
|
185
|
-
const tempPath = `${execPath}.new`;
|
|
186
|
-
const backupPath = `${execPath}.bak`;
|
|
242
|
+
const tag = `v${expectedVersion}`;
|
|
243
|
+
const url = `https://github.com/${REPO}/releases/download/${tag}/${binaryName}`;
|
|
244
|
+
|
|
245
|
+
const tempPath = `${targetPath}.new`;
|
|
246
|
+
const backupPath = `${targetPath}.bak`;
|
|
187
247
|
console.log(chalk.dim(`Downloading ${binaryName}…`));
|
|
188
248
|
|
|
189
|
-
|
|
190
|
-
const response = await fetch(asset.url, { redirect: "follow" });
|
|
249
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
191
250
|
if (!response.ok || !response.body) {
|
|
192
251
|
throw new Error(`Download failed: ${response.statusText}`);
|
|
193
252
|
}
|
|
194
253
|
const fileStream = fs.createWriteStream(tempPath, { mode: 0o755 });
|
|
195
254
|
await pipeline(response.body, fileStream);
|
|
196
|
-
|
|
255
|
+
|
|
197
256
|
console.log(chalk.dim("Installing update..."));
|
|
198
257
|
try {
|
|
199
258
|
try {
|
|
@@ -201,15 +260,15 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
|
201
260
|
} catch (err) {
|
|
202
261
|
if (!isEnoent(err)) throw err;
|
|
203
262
|
}
|
|
204
|
-
await fs.promises.rename(
|
|
205
|
-
await fs.promises.rename(tempPath,
|
|
263
|
+
await fs.promises.rename(targetPath, backupPath);
|
|
264
|
+
await fs.promises.rename(tempPath, targetPath);
|
|
206
265
|
await fs.promises.unlink(backupPath);
|
|
207
266
|
|
|
208
|
-
|
|
267
|
+
await printVerification(expectedVersion);
|
|
209
268
|
console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
|
|
210
269
|
} catch (err) {
|
|
211
|
-
if (fs.existsSync(backupPath) && !fs.existsSync(
|
|
212
|
-
await fs.promises.rename(backupPath,
|
|
270
|
+
if (fs.existsSync(backupPath) && !fs.existsSync(targetPath)) {
|
|
271
|
+
await fs.promises.rename(backupPath, targetPath);
|
|
213
272
|
}
|
|
214
273
|
if (fs.existsSync(tempPath)) {
|
|
215
274
|
await fs.promises.unlink(tempPath);
|
|
@@ -251,12 +310,13 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
251
310
|
return;
|
|
252
311
|
}
|
|
253
312
|
|
|
254
|
-
// Choose update method
|
|
313
|
+
// Choose update method based on the prioritized omp binary in PATH
|
|
255
314
|
try {
|
|
256
|
-
|
|
315
|
+
const target = await resolveUpdateTarget();
|
|
316
|
+
if (target.method === "bun") {
|
|
257
317
|
await updateViaBun(release.version);
|
|
258
318
|
} else {
|
|
259
|
-
await
|
|
319
|
+
await updateViaBinaryAt(target.path, release.version);
|
|
260
320
|
}
|
|
261
321
|
} catch (err) {
|
|
262
322
|
console.error(chalk.red(`Update failed: ${err}`));
|
|
@@ -3,15 +3,14 @@ import {
|
|
|
3
3
|
type AssistantMessageEventStream,
|
|
4
4
|
type Context,
|
|
5
5
|
createModelManager,
|
|
6
|
+
DEFAULT_LOCAL_TOKEN,
|
|
6
7
|
getBundledModels,
|
|
7
8
|
getBundledProviders,
|
|
8
|
-
getGitHubCopilotBaseUrl,
|
|
9
9
|
googleAntigravityModelManagerOptions,
|
|
10
10
|
googleGeminiCliModelManagerOptions,
|
|
11
11
|
type Model,
|
|
12
12
|
type ModelManagerOptions,
|
|
13
13
|
type ModelRefreshStrategy,
|
|
14
|
-
normalizeDomain,
|
|
15
14
|
type OAuthCredentials,
|
|
16
15
|
type OAuthLoginCallbacks,
|
|
17
16
|
openaiCodexModelManagerOptions,
|
|
@@ -99,6 +98,7 @@ const ModelDefinitionSchema = Type.Object({
|
|
|
99
98
|
cacheWrite: Type.Number(),
|
|
100
99
|
}),
|
|
101
100
|
),
|
|
101
|
+
premiumMultiplier: Type.Optional(Type.Number()),
|
|
102
102
|
contextWindow: Type.Optional(Type.Number()),
|
|
103
103
|
maxTokens: Type.Optional(Type.Number()),
|
|
104
104
|
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
@@ -119,6 +119,7 @@ const ModelOverrideSchema = Type.Object({
|
|
|
119
119
|
cacheWrite: Type.Optional(Type.Number()),
|
|
120
120
|
}),
|
|
121
121
|
),
|
|
122
|
+
premiumMultiplier: Type.Optional(Type.Number()),
|
|
122
123
|
contextWindow: Type.Optional(Type.Number()),
|
|
123
124
|
maxTokens: Type.Optional(Type.Number()),
|
|
124
125
|
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
@@ -129,7 +130,7 @@ const ModelOverrideSchema = Type.Object({
|
|
|
129
130
|
type ModelOverride = Static<typeof ModelOverrideSchema>;
|
|
130
131
|
|
|
131
132
|
const ProviderDiscoverySchema = Type.Object({
|
|
132
|
-
type: Type.Union([Type.Literal("ollama")]),
|
|
133
|
+
type: Type.Union([Type.Literal("ollama"), Type.Literal("lm-studio")]),
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
const ProviderAuthSchema = Type.Union([Type.Literal("apiKey"), Type.Literal("none")]);
|
|
@@ -378,6 +379,7 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
378
379
|
if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
|
|
379
380
|
if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
|
|
380
381
|
if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
|
|
382
|
+
if (override.premiumMultiplier !== undefined) result.premiumMultiplier = override.premiumMultiplier;
|
|
381
383
|
if (override.cost) {
|
|
382
384
|
result.cost = {
|
|
383
385
|
input: override.cost.input ?? model.cost.input,
|
|
@@ -405,6 +407,7 @@ interface CustomModelDefinitionLike {
|
|
|
405
407
|
headers?: Record<string, string>;
|
|
406
408
|
compat?: Model<Api>["compat"];
|
|
407
409
|
contextPromotionTarget?: string;
|
|
410
|
+
premiumMultiplier?: number;
|
|
408
411
|
}
|
|
409
412
|
|
|
410
413
|
interface CustomModelBuildOptions {
|
|
@@ -456,6 +459,7 @@ function buildCustomModel(
|
|
|
456
459
|
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
457
460
|
compat: modelDef.compat,
|
|
458
461
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
462
|
+
premiumMultiplier: modelDef.premiumMultiplier,
|
|
459
463
|
} as Model<Api>;
|
|
460
464
|
}
|
|
461
465
|
|
|
@@ -533,17 +537,7 @@ export class ModelRegistry {
|
|
|
533
537
|
const builtInModels = this.#loadBuiltInModels(overrides, modelOverrides);
|
|
534
538
|
const combined = this.#mergeCustomModels(builtInModels, customModels);
|
|
535
539
|
|
|
536
|
-
|
|
537
|
-
const copilotCred = this.authStorage.getOAuthCredential("github-copilot");
|
|
538
|
-
if (copilotCred) {
|
|
539
|
-
const domain = copilotCred.enterpriseUrl
|
|
540
|
-
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
|
|
541
|
-
: undefined;
|
|
542
|
-
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
|
|
543
|
-
this.#models = combined.map(m => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
|
544
|
-
} else {
|
|
545
|
-
this.#models = combined;
|
|
546
|
-
}
|
|
540
|
+
this.#models = combined;
|
|
547
541
|
}
|
|
548
542
|
|
|
549
543
|
/** Load built-in models, applying provider and per-model overrides */
|
|
@@ -589,14 +583,24 @@ export class ModelRegistry {
|
|
|
589
583
|
}
|
|
590
584
|
|
|
591
585
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
592
|
-
if (configuredProviders.has("ollama"))
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
586
|
+
if (!configuredProviders.has("ollama")) {
|
|
587
|
+
this.#discoverableProviders.push({
|
|
588
|
+
provider: "ollama",
|
|
589
|
+
api: "openai-completions",
|
|
590
|
+
baseUrl: Bun.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434",
|
|
591
|
+
discovery: { type: "ollama" },
|
|
592
|
+
});
|
|
593
|
+
this.#keylessProviders.add("ollama");
|
|
594
|
+
}
|
|
595
|
+
if (!configuredProviders.has("lm-studio")) {
|
|
596
|
+
this.#discoverableProviders.push({
|
|
597
|
+
provider: "lm-studio",
|
|
598
|
+
api: "openai-completions",
|
|
599
|
+
baseUrl: Bun.env.LM_STUDIO_BASE_URL || "http://127.0.0.1:1234/v1",
|
|
600
|
+
discovery: { type: "lm-studio" },
|
|
601
|
+
});
|
|
602
|
+
this.#keylessProviders.add("lm-studio");
|
|
603
|
+
}
|
|
600
604
|
}
|
|
601
605
|
|
|
602
606
|
#loadCustomModels(): CustomModelsResult {
|
|
@@ -719,6 +723,8 @@ export class ModelRegistry {
|
|
|
719
723
|
switch (providerConfig.discovery.type) {
|
|
720
724
|
case "ollama":
|
|
721
725
|
return this.#discoverOllamaModels(providerConfig);
|
|
726
|
+
case "lm-studio":
|
|
727
|
+
return this.#discoverLmStudioModels(providerConfig);
|
|
722
728
|
}
|
|
723
729
|
}
|
|
724
730
|
|
|
@@ -872,6 +878,77 @@ export class ModelRegistry {
|
|
|
872
878
|
}
|
|
873
879
|
}
|
|
874
880
|
|
|
881
|
+
async #discoverLmStudioModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
882
|
+
const baseUrl = this.#normalizeLmStudioBaseUrl(providerConfig.baseUrl);
|
|
883
|
+
const modelsUrl = `${baseUrl}/models`;
|
|
884
|
+
|
|
885
|
+
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
886
|
+
const apiKey = await this.authStorage.getApiKey("lm-studio");
|
|
887
|
+
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
888
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
try {
|
|
892
|
+
const response = await fetch(modelsUrl, {
|
|
893
|
+
headers,
|
|
894
|
+
signal: AbortSignal.timeout(3000),
|
|
895
|
+
});
|
|
896
|
+
if (!response.ok) {
|
|
897
|
+
logger.warn("model discovery failed for provider", {
|
|
898
|
+
provider: providerConfig.provider,
|
|
899
|
+
status: response.status,
|
|
900
|
+
url: modelsUrl,
|
|
901
|
+
});
|
|
902
|
+
return [];
|
|
903
|
+
}
|
|
904
|
+
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
905
|
+
const models = payload.data ?? [];
|
|
906
|
+
const discovered: Model<Api>[] = [];
|
|
907
|
+
for (const item of models) {
|
|
908
|
+
const id = item.id;
|
|
909
|
+
if (!id) continue;
|
|
910
|
+
discovered.push({
|
|
911
|
+
id,
|
|
912
|
+
name: id,
|
|
913
|
+
api: providerConfig.api,
|
|
914
|
+
provider: providerConfig.provider,
|
|
915
|
+
baseUrl,
|
|
916
|
+
reasoning: false,
|
|
917
|
+
input: ["text"],
|
|
918
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
919
|
+
contextWindow: 128000,
|
|
920
|
+
maxTokens: 8192,
|
|
921
|
+
headers,
|
|
922
|
+
compat: {
|
|
923
|
+
supportsStore: false,
|
|
924
|
+
supportsDeveloperRole: false,
|
|
925
|
+
supportsReasoningEffort: false,
|
|
926
|
+
},
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
930
|
+
} catch (error) {
|
|
931
|
+
logger.warn("model discovery failed for provider", {
|
|
932
|
+
provider: providerConfig.provider,
|
|
933
|
+
url: modelsUrl,
|
|
934
|
+
error: error instanceof Error ? error.message : String(error),
|
|
935
|
+
});
|
|
936
|
+
return [];
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
#normalizeLmStudioBaseUrl(baseUrl?: string): string {
|
|
941
|
+
const defaultBaseUrl = "http://127.0.0.1:1234/v1";
|
|
942
|
+
const raw = baseUrl || defaultBaseUrl;
|
|
943
|
+
try {
|
|
944
|
+
const parsed = new URL(raw);
|
|
945
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
946
|
+
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
|
|
947
|
+
return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
948
|
+
} catch {
|
|
949
|
+
return raw;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
875
952
|
#normalizeOllamaBaseUrl(baseUrl?: string): string {
|
|
876
953
|
const raw = baseUrl || "http://127.0.0.1:11434";
|
|
877
954
|
try {
|
|
@@ -1136,5 +1213,6 @@ export interface ProviderConfigInput {
|
|
|
1136
1213
|
headers?: Record<string, string>;
|
|
1137
1214
|
compat?: Model<Api>["compat"];
|
|
1138
1215
|
contextPromotionTarget?: string;
|
|
1216
|
+
premiumMultiplier?: number;
|
|
1139
1217
|
}>;
|
|
1140
1218
|
}
|
|
@@ -63,6 +63,7 @@ export type StatusLineSegmentId =
|
|
|
63
63
|
| "plan_mode"
|
|
64
64
|
| "path"
|
|
65
65
|
| "git"
|
|
66
|
+
| "pr"
|
|
66
67
|
| "subagents"
|
|
67
68
|
| "token_in"
|
|
68
69
|
| "token_out"
|
|
@@ -569,12 +570,13 @@ export const SETTINGS_SCHEMA = {
|
|
|
569
570
|
// ─────────────────────────────────────────────────────────────────────────
|
|
570
571
|
"task.isolation.mode": {
|
|
571
572
|
type: "enum",
|
|
572
|
-
values: ["none", "worktree", "fuse-overlay"] as const,
|
|
573
|
+
values: ["none", "worktree", "fuse-overlay", "fuse-projfs"] as const,
|
|
573
574
|
default: "none",
|
|
574
575
|
ui: {
|
|
575
576
|
tab: "tools",
|
|
576
577
|
label: "Task isolation",
|
|
577
|
-
description:
|
|
578
|
+
description:
|
|
579
|
+
"Isolation mode for subagents (none, git worktree, fuse-overlayfs on Unix, or ProjFS on Windows via fuse-projfs; unsupported modes fall back to worktree)",
|
|
578
580
|
submenu: true,
|
|
579
581
|
},
|
|
580
582
|
},
|
|
@@ -858,6 +860,24 @@ export const SETTINGS_SCHEMA = {
|
|
|
858
860
|
default: true,
|
|
859
861
|
ui: { tab: "tools", label: "MCP project config", description: "Load .mcp.json/mcp.json from project root" },
|
|
860
862
|
},
|
|
863
|
+
"mcp.notifications": {
|
|
864
|
+
type: "boolean",
|
|
865
|
+
default: false,
|
|
866
|
+
ui: {
|
|
867
|
+
tab: "tools",
|
|
868
|
+
label: "MCP update injection",
|
|
869
|
+
description: "Inject MCP resource updates into the agent conversation",
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
"mcp.notificationDebounceMs": {
|
|
873
|
+
type: "number",
|
|
874
|
+
default: 500,
|
|
875
|
+
ui: {
|
|
876
|
+
tab: "tools",
|
|
877
|
+
label: "MCP notification debounce (ms)",
|
|
878
|
+
description: "Debounce window for MCP resource update notifications before injecting into conversation",
|
|
879
|
+
},
|
|
880
|
+
},
|
|
861
881
|
|
|
862
882
|
// ─────────────────────────────────────────────────────────────────────────
|
|
863
883
|
// LSP settings
|
|
@@ -1144,6 +1144,8 @@ export interface ProviderModelConfig {
|
|
|
1144
1144
|
input: ("text" | "image")[];
|
|
1145
1145
|
/** Cost per million tokens. */
|
|
1146
1146
|
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
1147
|
+
/** Premium Copilot requests charged per user-initiated request. */
|
|
1148
|
+
premiumMultiplier?: number;
|
|
1147
1149
|
/** Maximum context window size in tokens. */
|
|
1148
1150
|
contextWindow: number;
|
|
1149
1151
|
/** Maximum output tokens. */
|