@oh-my-pi/pi-coding-agent 13.2.1 → 13.3.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 +43 -2
- package/package.json +7 -7
- package/scripts/generate-docs-index.ts +2 -2
- package/src/cli/args.ts +2 -1
- package/src/cli/config-cli.ts +32 -20
- package/src/config/settings-schema.ts +96 -14
- package/src/config/settings.ts +10 -0
- package/src/discovery/claude.ts +24 -6
- package/src/discovery/helpers.ts +9 -2
- package/src/ipy/runtime.ts +1 -0
- package/src/mcp/config.ts +1 -1
- package/src/modes/components/settings-defs.ts +53 -1
- package/src/modes/components/status-line.ts +7 -5
- package/src/modes/controllers/mcp-command-controller.ts +4 -3
- package/src/modes/controllers/selector-controller.ts +46 -0
- package/src/modes/interactive-mode.ts +9 -0
- package/src/modes/oauth-manual-input.ts +42 -0
- package/src/modes/types.ts +2 -0
- package/src/patch/hashline.ts +19 -1
- package/src/patch/index.ts +7 -8
- package/src/prompts/system/commit-message-system.md +2 -0
- package/src/prompts/system/subagent-submit-reminder.md +3 -3
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +13 -0
- package/src/prompts/tools/hashline.md +45 -1
- package/src/prompts/tools/task-summary.md +4 -4
- package/src/prompts/tools/task.md +1 -1
- package/src/sdk.ts +8 -0
- package/src/slash-commands/builtin-registry.ts +26 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/index.ts +211 -70
- package/src/task/render.ts +44 -16
- package/src/task/types.ts +6 -1
- package/src/task/worktree.ts +394 -31
- package/src/tools/review.ts +50 -1
- package/src/tools/submit-result.ts +22 -23
- package/src/utils/commit-message-generator.ts +132 -0
- package/src/web/search/providers/exa.ts +41 -4
- package/src/web/search/providers/perplexity.ts +20 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.3.1] - 2026-02-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `topP` setting to control nucleus sampling cutoff for model output diversity
|
|
10
|
+
- Added `topK` setting to sample from top-K tokens for controlled generation
|
|
11
|
+
- Added `minP` setting to enforce minimum probability threshold for token selection
|
|
12
|
+
- Added `presencePenalty` setting to penalize introduction of already-present tokens
|
|
13
|
+
- Added `repetitionPenalty` setting to penalize repeated tokens in model output
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed skill discovery to continue loading project skills when user skills directory is missing
|
|
18
|
+
|
|
19
|
+
## [13.3.0] - 2026-02-26
|
|
20
|
+
|
|
21
|
+
### Breaking Changes
|
|
22
|
+
|
|
23
|
+
- Renamed `task.isolation.enabled` (boolean) setting to `task.isolation.mode` (enum: `none`, `worktree`, `fuse-overlay`). Existing `true`/`false` values are auto-migrated to `worktree`/`none`.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Added `PERPLEXITY_COOKIES` env var for Perplexity web search via session cookies extracted from desktop app
|
|
28
|
+
- Added `fuse-overlay` isolation mode for subagents using `fuse-overlayfs` (copy-on-write overlay, no baseline patch apply needed)
|
|
29
|
+
- Added `task.isolation.merge` setting (`patch` or `branch`) to control how isolated task changes are integrated back. `branch` mode commits each task to a temp branch and cherry-picks for clean commit history
|
|
30
|
+
- Added `task.isolation.commits` setting (`generic` or `ai`) for commit messages on isolated task branches and nested repos. `ai` mode uses a smol model to generate conventional commit messages from diffs
|
|
31
|
+
- Nested non-submodule git repos are now discovered and handled during task isolation (changes captured and applied independently from parent repo)
|
|
32
|
+
- Added `task.eager` setting to encourage the agent to delegate work to subagents by default
|
|
33
|
+
- Added manual OAuth login flow that lets users paste redirect URLs with /login for callback-server providers and prevents overlapping logins
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Fixed nested repo changes being lost when tasks commit inside the isolation (baseline state is now committed before task runs, so delta correctly excludes it)
|
|
38
|
+
- Fixed nested repo patches conflicting when multiple tasks contribute to the same repo (baseline untracked files no longer leak into patches)
|
|
39
|
+
- Nested repo changes are now committed after patch application (previously left as untracked files)
|
|
40
|
+
- Failed tasks no longer create stale branches or capture garbage patches (gated on exit code)
|
|
41
|
+
- Merge failures (e.g. conflicting patches) are now non-fatal — agent output is preserved with `merge failed` status instead of `failed`
|
|
42
|
+
- Stale branches are cleaned up when `commitToBranch` fails
|
|
43
|
+
- Commit message generator filters lock files from diffs before AI summarization
|
|
44
|
+
|
|
5
45
|
## [13.2.1] - 2026-02-24
|
|
6
46
|
|
|
7
47
|
### Fixed
|
|
@@ -11,7 +51,6 @@
|
|
|
11
51
|
### Changed
|
|
12
52
|
|
|
13
53
|
- Extracted non-interactive environment config from `bash-interactive.ts` into shared `non-interactive-env.ts` module, applied consistently to all bash execution paths
|
|
14
|
-
|
|
15
54
|
## [13.2.0] - 2026-02-23
|
|
16
55
|
### Breaking Changes
|
|
17
56
|
|
|
@@ -34,12 +73,14 @@
|
|
|
34
73
|
- Removed unused SSH resource cleanup functions `closeAllConnections` and `unmountAll` from session imports
|
|
35
74
|
|
|
36
75
|
## [13.1.2] - 2026-02-23
|
|
37
|
-
### Breaking Changes
|
|
38
76
|
|
|
77
|
+
### Breaking Changes
|
|
39
78
|
- Removed `timeout` parameter from await tool—tool now waits indefinitely until jobs complete or the call is aborted
|
|
40
79
|
- Renamed `job_ids` parameter to `jobs` in await tool schema
|
|
41
80
|
- Removed `timedOut` field from await tool result details
|
|
42
81
|
|
|
82
|
+
### Changed
|
|
83
|
+
- Resolved docs index generation paths using path.resolve relative to the script directory
|
|
43
84
|
## [13.1.1] - 2026-02-23
|
|
44
85
|
|
|
45
86
|
### Fixed
|
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.3.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.3.1",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.3.1",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.3.1",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.3.1",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.3.1",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.3.1",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { Glob } from "bun";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
|
|
6
|
-
const docsDir =
|
|
7
|
-
const outputPath =
|
|
6
|
+
const docsDir = path.resolve(import.meta.dir, "../../../docs");
|
|
7
|
+
const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/docs-index.generated.ts");
|
|
8
8
|
|
|
9
9
|
const glob = new Glob("**/*.md");
|
|
10
10
|
const entries: string[] = [];
|
package/src/cli/args.ts
CHANGED
|
@@ -216,7 +216,8 @@ export function getExtraHelpText(): string {
|
|
|
216
216
|
${chalk.dim("# Search & Tools")}
|
|
217
217
|
EXA_API_KEY - Exa web search
|
|
218
218
|
BRAVE_API_KEY - Brave web search
|
|
219
|
-
PERPLEXITY_API_KEY - Perplexity web search
|
|
219
|
+
PERPLEXITY_API_KEY - Perplexity web search (API)
|
|
220
|
+
PERPLEXITY_COOKIES - Perplexity web search (session cookie)
|
|
220
221
|
ANTHROPIC_SEARCH_API_KEY - Anthropic search provider
|
|
221
222
|
|
|
222
223
|
${chalk.dim("# Configuration")}
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Config CLI command handlers.
|
|
3
3
|
*
|
|
4
4
|
* Handles `omp config <command>` subcommands for managing settings.
|
|
5
|
-
* Uses settings
|
|
5
|
+
* Uses the settings schema as the source of truth for available settings.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { APP_NAME, getAgentDir } from "@oh-my-pi/pi-utils";
|
|
@@ -11,12 +11,13 @@ import {
|
|
|
11
11
|
getDefault,
|
|
12
12
|
getEnumValues,
|
|
13
13
|
getType,
|
|
14
|
+
getUi,
|
|
14
15
|
type SettingPath,
|
|
15
16
|
Settings,
|
|
16
17
|
type SettingValue,
|
|
17
18
|
settings,
|
|
18
19
|
} from "../config/settings";
|
|
19
|
-
import {
|
|
20
|
+
import { SETTINGS_SCHEMA } from "../config/settings-schema";
|
|
20
21
|
import { theme } from "../modes/theme/theme";
|
|
21
22
|
|
|
22
23
|
// =============================================================================
|
|
@@ -38,21 +39,32 @@ export interface ConfigCommandArgs {
|
|
|
38
39
|
// Setting Filtering
|
|
39
40
|
// =============================================================================
|
|
40
41
|
|
|
42
|
+
type CliSettingDef = {
|
|
43
|
+
path: SettingPath;
|
|
44
|
+
type: string;
|
|
45
|
+
description: string;
|
|
46
|
+
tab: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ALL_SETTING_PATHS = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
|
|
50
|
+
|
|
41
51
|
/** Find setting definition by path */
|
|
42
|
-
function findSettingDef(path: string):
|
|
43
|
-
|
|
52
|
+
function findSettingDef(path: string): CliSettingDef | undefined {
|
|
53
|
+
if (!(path in SETTINGS_SCHEMA)) return undefined;
|
|
54
|
+
const key = path as SettingPath;
|
|
55
|
+
const ui = getUi(key);
|
|
56
|
+
return {
|
|
57
|
+
path: key,
|
|
58
|
+
type: getType(key),
|
|
59
|
+
description: ui?.description ?? "",
|
|
60
|
+
tab: ui?.tab ?? "internal",
|
|
61
|
+
};
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
/** Get available values for a setting */
|
|
47
|
-
function getSettingValues(def:
|
|
65
|
+
function getSettingValues(def: CliSettingDef): readonly string[] | undefined {
|
|
48
66
|
if (def.type === "enum") {
|
|
49
|
-
return def.
|
|
50
|
-
}
|
|
51
|
-
if (def.type === "submenu") {
|
|
52
|
-
const options = def.options;
|
|
53
|
-
if (options.length > 0) {
|
|
54
|
-
return options.map(o => o.value);
|
|
55
|
-
}
|
|
67
|
+
return getEnumValues(def.path);
|
|
56
68
|
}
|
|
57
69
|
return undefined;
|
|
58
70
|
}
|
|
@@ -125,7 +137,7 @@ function formatValue(value: unknown): string {
|
|
|
125
137
|
return chalk.yellow(String(value));
|
|
126
138
|
}
|
|
127
139
|
|
|
128
|
-
function getTypeDisplay(def:
|
|
140
|
+
function getTypeDisplay(def: CliSettingDef): string {
|
|
129
141
|
if (def.type === "boolean") {
|
|
130
142
|
return "(boolean)";
|
|
131
143
|
}
|
|
@@ -199,13 +211,13 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
|
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
function handleList(flags: { json?: boolean }): void {
|
|
202
|
-
const defs =
|
|
214
|
+
const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
|
|
203
215
|
|
|
204
216
|
if (flags.json) {
|
|
205
217
|
const result: Record<string, { value: unknown; type: string; description: string }> = {};
|
|
206
218
|
for (const def of defs) {
|
|
207
219
|
result[def.path] = {
|
|
208
|
-
value: settings.get(def.path
|
|
220
|
+
value: settings.get(def.path),
|
|
209
221
|
type: def.type,
|
|
210
222
|
description: def.description,
|
|
211
223
|
};
|
|
@@ -216,7 +228,7 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
216
228
|
|
|
217
229
|
console.log(chalk.bold("Settings:\n"));
|
|
218
230
|
|
|
219
|
-
const groups: Record<string,
|
|
231
|
+
const groups: Record<string, CliSettingDef[]> = {};
|
|
220
232
|
for (const def of defs) {
|
|
221
233
|
if (!groups[def.tab]) {
|
|
222
234
|
groups[def.tab] = [];
|
|
@@ -233,7 +245,7 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
233
245
|
for (const group of sortedGroups) {
|
|
234
246
|
console.log(chalk.bold.blue(`[${group}]`));
|
|
235
247
|
for (const def of groups[group]) {
|
|
236
|
-
const value = settings.get(def.path
|
|
248
|
+
const value = settings.get(def.path);
|
|
237
249
|
const valueStr = formatValue(value);
|
|
238
250
|
const typeStr = getTypeDisplay(def);
|
|
239
251
|
console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`);
|
|
@@ -256,7 +268,7 @@ function handleGet(key: string | undefined, flags: { json?: boolean }): void {
|
|
|
256
268
|
process.exit(1);
|
|
257
269
|
}
|
|
258
270
|
|
|
259
|
-
const value = settings.get(def.path
|
|
271
|
+
const value = settings.get(def.path);
|
|
260
272
|
|
|
261
273
|
if (flags.json) {
|
|
262
274
|
console.log(JSON.stringify({ key: def.path, value, type: def.type, description: def.description }, null, 2));
|
|
@@ -281,13 +293,13 @@ async function handleSet(key: string | undefined, value: string | undefined, fla
|
|
|
281
293
|
}
|
|
282
294
|
|
|
283
295
|
try {
|
|
284
|
-
parseAndSetValue(def.path
|
|
296
|
+
parseAndSetValue(def.path, value);
|
|
285
297
|
} catch (err) {
|
|
286
298
|
console.error(chalk.red(String(err)));
|
|
287
299
|
process.exit(1);
|
|
288
300
|
}
|
|
289
301
|
|
|
290
|
-
const newValue = settings.get(def.path
|
|
302
|
+
const newValue = settings.get(def.path);
|
|
291
303
|
|
|
292
304
|
if (flags.json) {
|
|
293
305
|
console.log(JSON.stringify({ key: def.path, value: newValue }));
|
|
@@ -197,16 +197,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
197
197
|
submenu: true,
|
|
198
198
|
},
|
|
199
199
|
},
|
|
200
|
-
temperature: {
|
|
201
|
-
type: "number",
|
|
202
|
-
default: -1,
|
|
203
|
-
ui: {
|
|
204
|
-
tab: "agent",
|
|
205
|
-
label: "Temperature",
|
|
206
|
-
description: "Sampling temperature (0 = deterministic, 1 = creative, -1 = provider default)",
|
|
207
|
-
submenu: true,
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
200
|
hideThinkingBlock: {
|
|
211
201
|
type: "boolean",
|
|
212
202
|
default: false,
|
|
@@ -544,16 +534,48 @@ export const SETTINGS_SCHEMA = {
|
|
|
544
534
|
// ─────────────────────────────────────────────────────────────────────────
|
|
545
535
|
// Task tool settings
|
|
546
536
|
// ─────────────────────────────────────────────────────────────────────────
|
|
547
|
-
"task.isolation.
|
|
548
|
-
type: "
|
|
549
|
-
|
|
537
|
+
"task.isolation.mode": {
|
|
538
|
+
type: "enum",
|
|
539
|
+
values: ["none", "worktree", "fuse-overlay"] as const,
|
|
540
|
+
default: "none",
|
|
550
541
|
ui: {
|
|
551
542
|
tab: "tools",
|
|
552
543
|
label: "Task isolation",
|
|
553
|
-
description: "
|
|
544
|
+
description: "Isolation mode for subagents (none, git worktree, or fuse-overlay)",
|
|
545
|
+
submenu: true,
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
"task.isolation.merge": {
|
|
549
|
+
type: "enum",
|
|
550
|
+
values: ["patch", "branch"] as const,
|
|
551
|
+
default: "patch",
|
|
552
|
+
ui: {
|
|
553
|
+
tab: "tools",
|
|
554
|
+
label: "Task isolation merge",
|
|
555
|
+
description: "How isolated task changes are integrated (patch apply or branch merge)",
|
|
554
556
|
submenu: true,
|
|
555
557
|
},
|
|
556
558
|
},
|
|
559
|
+
"task.isolation.commits": {
|
|
560
|
+
type: "enum",
|
|
561
|
+
values: ["generic", "ai"] as const,
|
|
562
|
+
default: "generic",
|
|
563
|
+
ui: {
|
|
564
|
+
tab: "tools",
|
|
565
|
+
label: "Task isolation commits",
|
|
566
|
+
description: "Commit message style for nested repo changes (generic or AI-generated)",
|
|
567
|
+
submenu: true,
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
"task.eager": {
|
|
571
|
+
type: "boolean",
|
|
572
|
+
default: false,
|
|
573
|
+
ui: {
|
|
574
|
+
tab: "tools",
|
|
575
|
+
label: "Eager task delegation",
|
|
576
|
+
description: "Encourage the agent to delegate work to subagents unless changes are trivial",
|
|
577
|
+
},
|
|
578
|
+
},
|
|
557
579
|
"task.maxConcurrency": {
|
|
558
580
|
type: "number",
|
|
559
581
|
default: 32,
|
|
@@ -1016,6 +1038,66 @@ export const SETTINGS_SCHEMA = {
|
|
|
1016
1038
|
"statusLine.leftSegments": { type: "array", default: [] as StatusLineSegmentId[] },
|
|
1017
1039
|
"statusLine.rightSegments": { type: "array", default: [] as StatusLineSegmentId[] },
|
|
1018
1040
|
"statusLine.segmentOptions": { type: "record", default: {} as Record<string, unknown> },
|
|
1041
|
+
temperature: {
|
|
1042
|
+
type: "number",
|
|
1043
|
+
default: -1,
|
|
1044
|
+
ui: {
|
|
1045
|
+
tab: "agent",
|
|
1046
|
+
label: "Temperature",
|
|
1047
|
+
description: "Sampling temperature (0 = deterministic, 1 = creative, -1 = provider default)",
|
|
1048
|
+
submenu: true,
|
|
1049
|
+
},
|
|
1050
|
+
},
|
|
1051
|
+
topP: {
|
|
1052
|
+
type: "number",
|
|
1053
|
+
default: -1,
|
|
1054
|
+
ui: {
|
|
1055
|
+
tab: "agent",
|
|
1056
|
+
label: "Top P",
|
|
1057
|
+
description: "Nucleus sampling cutoff (0-1, -1 = provider default)",
|
|
1058
|
+
submenu: true,
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
topK: {
|
|
1062
|
+
type: "number",
|
|
1063
|
+
default: -1,
|
|
1064
|
+
ui: {
|
|
1065
|
+
tab: "agent",
|
|
1066
|
+
label: "Top K",
|
|
1067
|
+
description: "Sample from top-K tokens (-1 = provider default)",
|
|
1068
|
+
submenu: true,
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
minP: {
|
|
1072
|
+
type: "number",
|
|
1073
|
+
default: -1,
|
|
1074
|
+
ui: {
|
|
1075
|
+
tab: "agent",
|
|
1076
|
+
label: "Min P",
|
|
1077
|
+
description: "Minimum probability threshold (0-1, -1 = provider default)",
|
|
1078
|
+
submenu: true,
|
|
1079
|
+
},
|
|
1080
|
+
},
|
|
1081
|
+
presencePenalty: {
|
|
1082
|
+
type: "number",
|
|
1083
|
+
default: -1,
|
|
1084
|
+
ui: {
|
|
1085
|
+
tab: "agent",
|
|
1086
|
+
label: "Presence penalty",
|
|
1087
|
+
description: "Penalty for introducing already-present tokens (-1 = provider default)",
|
|
1088
|
+
submenu: true,
|
|
1089
|
+
},
|
|
1090
|
+
},
|
|
1091
|
+
repetitionPenalty: {
|
|
1092
|
+
type: "number",
|
|
1093
|
+
default: -1,
|
|
1094
|
+
ui: {
|
|
1095
|
+
tab: "agent",
|
|
1096
|
+
label: "Repetition penalty",
|
|
1097
|
+
description: "Penalty for repeated tokens (-1 = provider default)",
|
|
1098
|
+
submenu: true,
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1019
1101
|
} as const;
|
|
1020
1102
|
|
|
1021
1103
|
// ═══════════════════════════════════════════════════════════════════════════
|
package/src/config/settings.ts
CHANGED
|
@@ -553,6 +553,16 @@ export class Settings {
|
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
+
// task.isolation.enabled (boolean) -> task.isolation.mode (enum)
|
|
557
|
+
const taskObj = raw.task as Record<string, unknown> | undefined;
|
|
558
|
+
const isolationObj = taskObj?.isolation as Record<string, unknown> | undefined;
|
|
559
|
+
if (isolationObj && "enabled" in isolationObj) {
|
|
560
|
+
if (typeof isolationObj.enabled === "boolean") {
|
|
561
|
+
isolationObj.mode = isolationObj.enabled ? "worktree" : "none";
|
|
562
|
+
}
|
|
563
|
+
delete isolationObj.enabled;
|
|
564
|
+
}
|
|
565
|
+
|
|
556
566
|
return raw;
|
|
557
567
|
}
|
|
558
568
|
|
package/src/discovery/claude.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Priority: 80 (tool-specific, below builtin but above shared standards)
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { tryParseJson } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { hasFsCode, tryParseJson } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import { registerProvider } from "../capability";
|
|
10
10
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
11
11
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
@@ -47,6 +47,10 @@ function getProjectClaude(ctx: LoadContext): string {
|
|
|
47
47
|
return path.join(ctx.cwd, CONFIG_DIR);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function isMissingDirectoryError(error: unknown): boolean {
|
|
51
|
+
return hasFsCode(error, "ENOENT") || hasFsCode(error, "ENOTDIR");
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
// =============================================================================
|
|
51
55
|
// MCP Servers
|
|
52
56
|
// =============================================================================
|
|
@@ -162,15 +166,29 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
|
162
166
|
const userSkillsDir = path.join(getUserClaude(ctx), "skills");
|
|
163
167
|
const projectSkillsDir = path.join(getProjectClaude(ctx), "skills");
|
|
164
168
|
|
|
165
|
-
const
|
|
169
|
+
const [userResult, projectResult] = await Promise.allSettled([
|
|
166
170
|
scanSkillsFromDir(ctx, { dir: userSkillsDir, providerId: PROVIDER_ID, level: "user" }),
|
|
167
171
|
scanSkillsFromDir(ctx, { dir: projectSkillsDir, providerId: PROVIDER_ID, level: "project" }),
|
|
168
172
|
]);
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
const items: Skill[] = [];
|
|
175
|
+
const warnings: string[] = [];
|
|
176
|
+
|
|
177
|
+
if (userResult.status === "fulfilled") {
|
|
178
|
+
items.push(...userResult.value.items);
|
|
179
|
+
warnings.push(...(userResult.value.warnings ?? []));
|
|
180
|
+
} else if (!isMissingDirectoryError(userResult.reason)) {
|
|
181
|
+
warnings.push(`Failed to scan Claude user skills in ${userSkillsDir}: ${String(userResult.reason)}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (projectResult.status === "fulfilled") {
|
|
185
|
+
items.push(...projectResult.value.items);
|
|
186
|
+
warnings.push(...(projectResult.value.warnings ?? []));
|
|
187
|
+
} else if (!isMissingDirectoryError(projectResult.reason)) {
|
|
188
|
+
warnings.push(`Failed to scan Claude project skills in ${projectSkillsDir}: ${String(projectResult.reason)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { items, warnings };
|
|
174
192
|
}
|
|
175
193
|
|
|
176
194
|
// =============================================================================
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -284,8 +284,15 @@ export async function scanSkillsFromDir(
|
|
|
284
284
|
const warnings: string[] = [];
|
|
285
285
|
const { dir, level, providerId, requireDescription = false } = options;
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
let entries: fs.Dirent[];
|
|
288
|
+
try {
|
|
289
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
292
|
+
warnings.push(`Failed to read skills directory: ${dir} (${String(error)})`);
|
|
293
|
+
}
|
|
294
|
+
return { items, warnings };
|
|
295
|
+
}
|
|
289
296
|
const loadSkill = async (skillPath: string) => {
|
|
290
297
|
try {
|
|
291
298
|
const content = await readFile(skillPath);
|
package/src/ipy/runtime.ts
CHANGED
package/src/mcp/config.ts
CHANGED
|
@@ -110,7 +110,7 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt
|
|
|
110
110
|
let sources: Record<string, SourceMeta> = {};
|
|
111
111
|
for (const server of servers) {
|
|
112
112
|
const config = convertToLegacyConfig(server);
|
|
113
|
-
if (config.enabled === false ||
|
|
113
|
+
if (config.enabled === false || disabledServers.has(server.name)) {
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
116
|
configs[server.name] = config;
|
|
@@ -93,6 +93,22 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
93
93
|
{ value: "2", label: "Double" },
|
|
94
94
|
{ value: "3", label: "Triple" },
|
|
95
95
|
],
|
|
96
|
+
// Task isolation mode
|
|
97
|
+
"task.isolation.mode": [
|
|
98
|
+
{ value: "none", label: "None", description: "No isolation" },
|
|
99
|
+
{ value: "worktree", label: "Worktree", description: "Git worktree isolation" },
|
|
100
|
+
{ value: "fuse-overlay", label: "Fuse Overlay", description: "COW overlay via fuse-overlayfs" },
|
|
101
|
+
],
|
|
102
|
+
// Task isolation merge strategy
|
|
103
|
+
"task.isolation.merge": [
|
|
104
|
+
{ value: "patch", label: "Patch", description: "Combine diffs and git apply" },
|
|
105
|
+
{ value: "branch", label: "Branch", description: "Commit per task, merge with --no-ff" },
|
|
106
|
+
],
|
|
107
|
+
// Task isolation commit messages
|
|
108
|
+
"task.isolation.commits": [
|
|
109
|
+
{ value: "generic", label: "Generic", description: "Static commit message" },
|
|
110
|
+
{ value: "ai", label: "AI", description: "AI-generated commit message from diff" },
|
|
111
|
+
],
|
|
96
112
|
// Todo max reminders
|
|
97
113
|
"todo.reminders.max": [
|
|
98
114
|
{ value: "1", label: "1 reminder" },
|
|
@@ -166,7 +182,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
166
182
|
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
167
183
|
{ value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
|
|
168
184
|
{ value: "kimi", label: "Kimi", description: "Requires MOONSHOT_SEARCH_API_KEY or MOONSHOT_API_KEY" },
|
|
169
|
-
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_API_KEY" },
|
|
185
|
+
{ value: "perplexity", label: "Perplexity", description: "Requires PERPLEXITY_COOKIES or PERPLEXITY_API_KEY" },
|
|
170
186
|
{ value: "anthropic", label: "Anthropic", description: "Uses Anthropic web search" },
|
|
171
187
|
{ value: "zai", label: "Z.AI", description: "Calls Z.AI webSearchPrime MCP" },
|
|
172
188
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
@@ -198,6 +214,42 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
198
214
|
{ value: "0.7", label: "0.7", description: "Creative" },
|
|
199
215
|
{ value: "1", label: "1", description: "Maximum variety" },
|
|
200
216
|
],
|
|
217
|
+
topP: [
|
|
218
|
+
{ value: "-1", label: "Default", description: "Use provider default" },
|
|
219
|
+
{ value: "0.1", label: "0.1", description: "Very focused" },
|
|
220
|
+
{ value: "0.3", label: "0.3", description: "Focused" },
|
|
221
|
+
{ value: "0.5", label: "0.5", description: "Balanced" },
|
|
222
|
+
{ value: "0.9", label: "0.9", description: "Broad" },
|
|
223
|
+
{ value: "1", label: "1", description: "No nucleus filtering" },
|
|
224
|
+
],
|
|
225
|
+
topK: [
|
|
226
|
+
{ value: "-1", label: "Default", description: "Use provider default" },
|
|
227
|
+
{ value: "1", label: "1", description: "Greedy top token" },
|
|
228
|
+
{ value: "20", label: "20", description: "Focused" },
|
|
229
|
+
{ value: "40", label: "40", description: "Balanced" },
|
|
230
|
+
{ value: "100", label: "100", description: "Broad" },
|
|
231
|
+
],
|
|
232
|
+
minP: [
|
|
233
|
+
{ value: "-1", label: "Default", description: "Use provider default" },
|
|
234
|
+
{ value: "0.01", label: "0.01", description: "Very permissive" },
|
|
235
|
+
{ value: "0.05", label: "0.05", description: "Balanced" },
|
|
236
|
+
{ value: "0.1", label: "0.1", description: "Strict" },
|
|
237
|
+
],
|
|
238
|
+
presencePenalty: [
|
|
239
|
+
{ value: "-1", label: "Default", description: "Use provider default" },
|
|
240
|
+
{ value: "0", label: "0", description: "No penalty" },
|
|
241
|
+
{ value: "0.5", label: "0.5", description: "Mild novelty" },
|
|
242
|
+
{ value: "1", label: "1", description: "Encourage novelty" },
|
|
243
|
+
{ value: "2", label: "2", description: "Strong novelty" },
|
|
244
|
+
],
|
|
245
|
+
repetitionPenalty: [
|
|
246
|
+
{ value: "-1", label: "Default", description: "Use provider default" },
|
|
247
|
+
{ value: "0.8", label: "0.8", description: "Allow repetition" },
|
|
248
|
+
{ value: "1", label: "1", description: "No penalty" },
|
|
249
|
+
{ value: "1.1", label: "1.1", description: "Mild penalty" },
|
|
250
|
+
{ value: "1.2", label: "1.2", description: "Balanced" },
|
|
251
|
+
{ value: "1.5", label: "1.5", description: "Strong penalty" },
|
|
252
|
+
],
|
|
201
253
|
// Symbol preset
|
|
202
254
|
symbolPreset: [
|
|
203
255
|
{ value: "unicode", label: "Unicode", description: "Standard symbols (default)" },
|
|
@@ -50,6 +50,7 @@ export class StatusLineComponent implements Component {
|
|
|
50
50
|
// Git status caching (1s TTL)
|
|
51
51
|
#cachedGitStatus: { staged: number; unstaged: number; untracked: number } | null = null;
|
|
52
52
|
#gitStatusLastFetch = 0;
|
|
53
|
+
#gitStatusInFlight = false;
|
|
53
54
|
|
|
54
55
|
constructor(private readonly session: AgentSession) {
|
|
55
56
|
this.#settings = {
|
|
@@ -153,11 +154,12 @@ export class StatusLineComponent implements Component {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
#getGitStatus(): { staged: number; unstaged: number; untracked: number } | null {
|
|
156
|
-
|
|
157
|
-
if (now - this.#gitStatusLastFetch < 1000) {
|
|
157
|
+
if (this.#gitStatusInFlight || Date.now() - this.#gitStatusLastFetch < 1000) {
|
|
158
158
|
return this.#cachedGitStatus;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
this.#gitStatusInFlight = true;
|
|
162
|
+
|
|
161
163
|
// Fire async fetch, return cached value
|
|
162
164
|
(async () => {
|
|
163
165
|
try {
|
|
@@ -165,7 +167,6 @@ export class StatusLineComponent implements Component {
|
|
|
165
167
|
|
|
166
168
|
if (result.exitCode !== 0) {
|
|
167
169
|
this.#cachedGitStatus = null;
|
|
168
|
-
this.#gitStatusLastFetch = now;
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -195,10 +196,11 @@ export class StatusLineComponent implements Component {
|
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
this.#cachedGitStatus = { staged, unstaged, untracked };
|
|
198
|
-
this.#gitStatusLastFetch = now;
|
|
199
199
|
} catch {
|
|
200
200
|
this.#cachedGitStatus = null;
|
|
201
|
-
|
|
201
|
+
} finally {
|
|
202
|
+
this.#gitStatusLastFetch = Date.now();
|
|
203
|
+
this.#gitStatusInFlight = false;
|
|
202
204
|
}
|
|
203
205
|
})();
|
|
204
206
|
|
|
@@ -739,10 +739,12 @@ export class MCPCommandController {
|
|
|
739
739
|
|
|
740
740
|
// Collect runtime-discovered servers not in config files
|
|
741
741
|
const configServerNames = new Set([...userServers, ...projectServers]);
|
|
742
|
+
const disabledServerNames = new Set(await readDisabledServers(userPath));
|
|
742
743
|
const discoveredServers: { name: string; source: SourceMeta }[] = [];
|
|
743
744
|
if (this.ctx.mcpManager) {
|
|
744
745
|
for (const name of this.ctx.mcpManager.getAllServerNames()) {
|
|
745
746
|
if (configServerNames.has(name)) continue;
|
|
747
|
+
if (disabledServerNames.has(name)) continue;
|
|
746
748
|
const source = this.ctx.mcpManager.getSource(name);
|
|
747
749
|
if (source) {
|
|
748
750
|
discoveredServers.push({ name, source });
|
|
@@ -754,7 +756,7 @@ export class MCPCommandController {
|
|
|
754
756
|
userServers.length === 0 &&
|
|
755
757
|
projectServers.length === 0 &&
|
|
756
758
|
discoveredServers.length === 0 &&
|
|
757
|
-
|
|
759
|
+
disabledServerNames.size === 0
|
|
758
760
|
) {
|
|
759
761
|
this.#showMessage(
|
|
760
762
|
[
|
|
@@ -851,8 +853,7 @@ export class MCPCommandController {
|
|
|
851
853
|
}
|
|
852
854
|
|
|
853
855
|
// Show servers disabled via /mcp disable (from third-party configs)
|
|
854
|
-
const
|
|
855
|
-
const relevantDisabled = disabledServers.filter(n => !configServerNames.has(n));
|
|
856
|
+
const relevantDisabled = [...disabledServerNames].filter(n => !configServerNames.has(n));
|
|
856
857
|
if (relevantDisabled.length > 0) {
|
|
857
858
|
lines.push(theme.fg("accent", "Disabled") + theme.fg("muted", " (discovered servers):"));
|
|
858
859
|
for (const name of relevantDisabled) {
|