@oh-my-pi/pi-coding-agent 14.7.0 → 14.7.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 +24 -0
- package/package.json +12 -12
- package/src/cli/grep-cli.ts +1 -1
- package/src/config/model-equivalence.ts +1 -0
- package/src/config/model-registry.ts +108 -22
- package/src/config/settings-schema.ts +46 -1
- package/src/config/settings.ts +71 -1
- package/src/dap/client.ts +1 -0
- package/src/discovery/builtin.ts +34 -9
- package/src/discovery/helpers.ts +4 -3
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/hashline.ts +212 -63
- package/src/eval/py/gateway-coordinator.ts +2 -3
- package/src/eval/py/runtime.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/lsp/index.ts +2 -0
- package/src/main.ts +10 -15
- package/src/mcp/discoverable-tool-metadata.ts +24 -202
- package/src/modes/components/extensions/extension-dashboard.ts +26 -2
- package/src/modes/components/extensions/state-manager.ts +41 -0
- package/src/modes/controllers/selector-controller.ts +3 -0
- package/src/modes/interactive-mode.ts +45 -13
- package/src/prompts/system/plan-mode-active.md +7 -3
- package/src/prompts/system/plan-mode-approved.md +5 -0
- package/src/prompts/tools/search-tool-bm25.md +14 -14
- package/src/prompts/tools/todo-write.md +1 -0
- package/src/sdk.ts +69 -8
- package/src/session/agent-session.ts +177 -1
- package/src/slash-commands/builtin-registry.ts +13 -2
- package/src/task/index.ts +2 -0
- package/src/task/isolation-backend.ts +22 -0
- package/src/tool-discovery/tool-index.ts +377 -0
- package/src/tools/ask.ts +2 -0
- package/src/tools/ast-edit.ts +2 -0
- package/src/tools/ast-grep.ts +2 -0
- package/src/tools/bash.ts +1 -0
- package/src/tools/browser.ts +2 -0
- package/src/tools/calculator.ts +2 -0
- package/src/tools/checkpoint.ts +4 -0
- package/src/tools/debug.ts +2 -0
- package/src/tools/eval.ts +2 -0
- package/src/tools/find.ts +2 -0
- package/src/tools/gh.ts +2 -0
- package/src/tools/hindsight-recall.ts +2 -0
- package/src/tools/hindsight-reflect.ts +2 -0
- package/src/tools/hindsight-retain.ts +2 -0
- package/src/tools/index.ts +74 -14
- package/src/tools/inspect-image.ts +2 -0
- package/src/tools/irc.ts +2 -1
- package/src/tools/job.ts +2 -1
- package/src/tools/notebook.ts +2 -0
- package/src/tools/read.ts +7 -1
- package/src/tools/recipe/index.ts +2 -0
- package/src/tools/render-mermaid.ts +2 -0
- package/src/tools/search-tool-bm25.ts +128 -42
- package/src/tools/search.ts +2 -0
- package/src/tools/ssh.ts +2 -0
- package/src/tools/todo-write.ts +2 -1
- package/src/tools/write.ts +2 -0
- package/src/web/search/index.ts +2 -0
- package/src/web/search/providers/searxng.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [14.7.2] - 2026-05-06
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Removed the exported `BUILTIN_TOOL_METADATA` API, including `BuiltinEntry`-style metadata exports and discoverable-built-in helper exports, which will break consumers relying on those symbols
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Updated discoverable tool search (`search_tool_bm25` and related discovery metadata) to read each tool’s own `summary` field when present, improving discoverability descriptions for built-in tools
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed SearXNG web search Basic Auth validation to reject RFC 7617 control characters and clarified the equivalent `config.yml` and environment variable settings.
|
|
17
|
+
- Fixed extension commands that return without starting a model turn leaving the interactive `Working…` spinner active indefinitely. (#927)
|
|
18
|
+
- Fixed `authHeader: true` provider overrides without custom `models` so built-in model transport headers receive `Authorization: Bearer <resolved-key>` (#929).
|
|
19
|
+
|
|
20
|
+
## [14.7.1] - 2026-05-06
|
|
21
|
+
|
|
4
22
|
### Added
|
|
5
23
|
|
|
6
24
|
- Added `pr_create` operation to the GitHub tool to create pull requests with title/body (or `fill`), base/head branch, draft, reviewer, assignee, and label options and return a summarized result including the new PR URL
|
|
25
|
+
- Added `read.summarize.prose` setting to keep Markdown and plain-text reads out of the structural summarizer by default.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Changed the `PI_GREP_WORKERS` environment variable help text to state that it sets filesystem walker workers, defaults to 4, and uses `0` for automatic worker selection
|
|
30
|
+
- Changed hashline replacement and pure-insert auto-absorb to also drop a single duplicated structural-closing line (`}`, `);`, `]`, etc.) on either boundary when keeping it would unbalance brackets. The pure-insert variant fires regardless of `edit.hashlineAutoDropPureInsertDuplicates`, while the existing 2+ line generic absorb stays gated on that setting.
|
|
7
31
|
|
|
8
32
|
## [14.7.0] - 2026-05-04
|
|
9
33
|
### Breaking Changes
|
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": "14.7.
|
|
4
|
+
"version": "14.7.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",
|
|
@@ -44,16 +44,17 @@
|
|
|
44
44
|
"generate-template": "bun scripts/generate-template.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@agentclientprotocol/sdk": "0.
|
|
47
|
+
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@mozilla/readability": "^0.6.0",
|
|
49
|
-
"@oh-my-pi/omp-stats": "14.7.
|
|
50
|
-
"@oh-my-pi/pi-agent-core": "14.7.
|
|
51
|
-
"@oh-my-pi/pi-ai": "14.7.
|
|
52
|
-
"@oh-my-pi/pi-natives": "14.7.
|
|
53
|
-
"@oh-my-pi/pi-tui": "14.7.
|
|
54
|
-
"@oh-my-pi/pi-utils": "14.7.
|
|
49
|
+
"@oh-my-pi/omp-stats": "14.7.2",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "14.7.2",
|
|
51
|
+
"@oh-my-pi/pi-ai": "14.7.2",
|
|
52
|
+
"@oh-my-pi/pi-natives": "14.7.2",
|
|
53
|
+
"@oh-my-pi/pi-tui": "14.7.2",
|
|
54
|
+
"@oh-my-pi/pi-utils": "14.7.2",
|
|
55
55
|
"@puppeteer/browsers": "^2.13.0",
|
|
56
56
|
"@sinclair/typebox": "^0.34.49",
|
|
57
|
+
"@types/turndown": "5.0.6",
|
|
57
58
|
"@xterm/headless": "^6.0.0",
|
|
58
59
|
"ajv": "^8.20.0",
|
|
59
60
|
"chalk": "^5.6.2",
|
|
@@ -61,16 +62,15 @@
|
|
|
61
62
|
"fflate": "0.8.2",
|
|
62
63
|
"handlebars": "^4.7.9",
|
|
63
64
|
"linkedom": "^0.18.12",
|
|
64
|
-
"lru-cache": "11.3.
|
|
65
|
+
"lru-cache": "11.3.6",
|
|
65
66
|
"markit-ai": "0.5.3",
|
|
66
67
|
"puppeteer-core": "^24.42.0",
|
|
67
68
|
"turndown": "7.2.4",
|
|
68
69
|
"turndown-plugin-gfm": "1.0.2",
|
|
69
|
-
"zod": "4.3
|
|
70
|
+
"zod": "4.4.3"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
|
-
"@types/bun": "^1.3"
|
|
73
|
-
"@types/turndown": "5.0.6"
|
|
73
|
+
"@types/bun": "^1.3.13"
|
|
74
74
|
},
|
|
75
75
|
"engines": {
|
|
76
76
|
"bun": ">=1.3.7"
|
package/src/cli/grep-cli.ts
CHANGED
|
@@ -150,7 +150,7 @@ ${chalk.bold("Options:")}
|
|
|
150
150
|
--no-gitignore Include files excluded by .gitignore
|
|
151
151
|
|
|
152
152
|
${chalk.bold("Environment:")}
|
|
153
|
-
PI_GREP_WORKERS=
|
|
153
|
+
PI_GREP_WORKERS=N Set filesystem walker workers (default 4, 0 = auto)
|
|
154
154
|
|
|
155
155
|
${chalk.bold("Examples:")}
|
|
156
156
|
${APP_NAME} grep "import" src/
|
|
@@ -421,6 +421,7 @@ interface ProviderOverride {
|
|
|
421
421
|
baseUrl?: string;
|
|
422
422
|
headers?: Record<string, string>;
|
|
423
423
|
apiKey?: string;
|
|
424
|
+
authHeader?: boolean;
|
|
424
425
|
compat?: Model<Api>["compat"];
|
|
425
426
|
}
|
|
426
427
|
|
|
@@ -667,14 +668,20 @@ function mergeCustomModelHeaders(
|
|
|
667
668
|
authHeader: boolean | undefined,
|
|
668
669
|
apiKeyConfig: string | undefined,
|
|
669
670
|
): Record<string, string> | undefined {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
671
|
+
return mergeAuthHeader({ ...providerHeaders, ...modelHeaders }, authHeader, apiKeyConfig);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function mergeAuthHeader(
|
|
675
|
+
headers: Record<string, string> | undefined,
|
|
676
|
+
authHeader: boolean | undefined,
|
|
677
|
+
apiKeyConfig: string | undefined,
|
|
678
|
+
): Record<string, string> | undefined {
|
|
679
|
+
const nextHeaders = headers && Object.keys(headers).length > 0 ? { ...headers } : undefined;
|
|
680
|
+
if (!authHeader || !apiKeyConfig) {
|
|
681
|
+
return nextHeaders;
|
|
676
682
|
}
|
|
677
|
-
|
|
683
|
+
const resolvedKey = resolveApiKeyConfig(apiKeyConfig);
|
|
684
|
+
return resolvedKey ? { ...nextHeaders, Authorization: `Bearer ${resolvedKey}` } : nextHeaders;
|
|
678
685
|
}
|
|
679
686
|
|
|
680
687
|
/**
|
|
@@ -726,6 +733,69 @@ function buildCustomModelOverlay(
|
|
|
726
733
|
};
|
|
727
734
|
}
|
|
728
735
|
|
|
736
|
+
// Custom provider entries often front a known upstream model through a local proxy.
|
|
737
|
+
// Use bundled metadata for missing pricing/capability fields, but keep the custom transport.
|
|
738
|
+
function shouldReplaceCustomReference(existing: Model<Api> | undefined, candidate: Model<Api>): boolean {
|
|
739
|
+
if (!existing) return true;
|
|
740
|
+
if (candidate.contextWindow !== existing.contextWindow) {
|
|
741
|
+
return candidate.contextWindow > existing.contextWindow;
|
|
742
|
+
}
|
|
743
|
+
if (candidate.maxTokens !== existing.maxTokens) {
|
|
744
|
+
return candidate.maxTokens > existing.maxTokens;
|
|
745
|
+
}
|
|
746
|
+
const existingHasCachePricing = existing.cost.cacheRead > 0 || existing.cost.cacheWrite > 0;
|
|
747
|
+
const candidateHasCachePricing = candidate.cost.cacheRead > 0 || candidate.cost.cacheWrite > 0;
|
|
748
|
+
if (candidateHasCachePricing !== existingHasCachePricing) {
|
|
749
|
+
return candidateHasCachePricing;
|
|
750
|
+
}
|
|
751
|
+
return existing.provider !== "openai" && candidate.provider === "openai";
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function buildCustomReferenceMap(): Map<string, Model<Api>> {
|
|
755
|
+
const references = new Map<string, Model<Api>>();
|
|
756
|
+
for (const provider of getBundledProviders()) {
|
|
757
|
+
for (const model of getBundledModels(provider as Parameters<typeof getBundledModels>[0])) {
|
|
758
|
+
const candidate = model as Model<Api>;
|
|
759
|
+
if (shouldReplaceCustomReference(references.get(candidate.id), candidate)) {
|
|
760
|
+
references.set(candidate.id, candidate);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return references;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const customReferenceMap = buildCustomReferenceMap();
|
|
768
|
+
|
|
769
|
+
function getCustomReferenceCandidateIds(modelId: string): string[] {
|
|
770
|
+
const candidates = new Set<string>();
|
|
771
|
+
const queue = [modelId];
|
|
772
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
773
|
+
const candidate = queue[index]?.trim();
|
|
774
|
+
if (!candidate || candidates.has(candidate)) continue;
|
|
775
|
+
candidates.add(candidate);
|
|
776
|
+
|
|
777
|
+
for (const suffix of [":cloud", "-cloud"] as const) {
|
|
778
|
+
if (candidate.toLowerCase().endsWith(suffix)) {
|
|
779
|
+
queue.push(candidate.slice(0, -suffix.length));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const colonToDash = candidate.replace(/:/g, "-");
|
|
784
|
+
if (colonToDash !== candidate) {
|
|
785
|
+
queue.push(colonToDash);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return [...candidates];
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function resolveCustomModelReference(modelId: string): Model<Api> | undefined {
|
|
792
|
+
for (const candidate of getCustomReferenceCandidateIds(modelId)) {
|
|
793
|
+
const reference = customReferenceMap.get(candidate);
|
|
794
|
+
if (reference) return reference;
|
|
795
|
+
}
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
798
|
+
|
|
729
799
|
function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
|
|
730
800
|
if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
|
|
731
801
|
return model;
|
|
@@ -735,23 +805,27 @@ function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomMo
|
|
|
735
805
|
|
|
736
806
|
function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
|
|
737
807
|
const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
|
|
808
|
+
const reference = options.useDefaults ? resolveCustomModelReference(resolvedModel.id) : undefined;
|
|
738
809
|
const cost =
|
|
739
|
-
resolvedModel.cost ??
|
|
740
|
-
|
|
810
|
+
resolvedModel.cost ??
|
|
811
|
+
reference?.cost ??
|
|
812
|
+
(options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
|
|
813
|
+
const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
|
|
741
814
|
return enrichModelThinking({
|
|
742
815
|
id: resolvedModel.id,
|
|
743
816
|
name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
|
|
744
817
|
api: resolvedModel.api,
|
|
745
818
|
provider: resolvedModel.provider,
|
|
746
819
|
baseUrl: resolvedModel.baseUrl,
|
|
747
|
-
reasoning: resolvedModel.reasoning ?? (options.useDefaults ? false : undefined),
|
|
748
|
-
thinking: resolvedModel.thinking,
|
|
820
|
+
reasoning: resolvedModel.reasoning ?? reference?.reasoning ?? (options.useDefaults ? false : undefined),
|
|
821
|
+
thinking: resolvedModel.thinking ?? reference?.thinking,
|
|
749
822
|
input: input as ("text" | "image")[],
|
|
750
823
|
cost,
|
|
751
|
-
contextWindow:
|
|
752
|
-
|
|
824
|
+
contextWindow:
|
|
825
|
+
resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : undefined),
|
|
826
|
+
maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : undefined),
|
|
753
827
|
headers: resolvedModel.headers,
|
|
754
|
-
compat: resolvedModel.compat,
|
|
828
|
+
compat: mergeCompat(reference?.compat, resolvedModel.compat),
|
|
755
829
|
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
756
830
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
757
831
|
isOAuth: resolvedModel.isOAuth,
|
|
@@ -954,10 +1028,9 @@ export class ModelRegistry {
|
|
|
954
1028
|
|
|
955
1029
|
return models.map(m => {
|
|
956
1030
|
if (!providerOverride) return m;
|
|
1031
|
+
const withTransportOverride = this.#applyProviderTransportOverride(m, providerOverride);
|
|
957
1032
|
return {
|
|
958
|
-
...
|
|
959
|
-
baseUrl: providerOverride.baseUrl ?? m.baseUrl,
|
|
960
|
-
headers: providerOverride.headers ? { ...m.headers, ...providerOverride.headers } : m.headers,
|
|
1033
|
+
...withTransportOverride,
|
|
961
1034
|
compat: mergeCompat(m.compat, providerOverride.compat),
|
|
962
1035
|
};
|
|
963
1036
|
});
|
|
@@ -1143,11 +1216,12 @@ export class ModelRegistry {
|
|
|
1143
1216
|
const configuredProviders = new Set(Object.keys(value.providers ?? {}));
|
|
1144
1217
|
|
|
1145
1218
|
for (const [providerName, providerConfig] of providerEntries) {
|
|
1146
|
-
// Always set overrides when baseUrl/headers/apiKey/compat/disableStrictTools are present
|
|
1219
|
+
// Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools are present
|
|
1147
1220
|
if (
|
|
1148
1221
|
providerConfig.baseUrl ||
|
|
1149
1222
|
providerConfig.headers ||
|
|
1150
1223
|
providerConfig.apiKey ||
|
|
1224
|
+
providerConfig.authHeader !== undefined ||
|
|
1151
1225
|
providerConfig.compat ||
|
|
1152
1226
|
providerConfig.disableStrictTools
|
|
1153
1227
|
) {
|
|
@@ -1156,6 +1230,7 @@ export class ModelRegistry {
|
|
|
1156
1230
|
baseUrl: providerConfig.baseUrl,
|
|
1157
1231
|
headers: providerConfig.headers,
|
|
1158
1232
|
apiKey: providerConfig.apiKey,
|
|
1233
|
+
authHeader: providerConfig.authHeader,
|
|
1159
1234
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1160
1235
|
});
|
|
1161
1236
|
}
|
|
@@ -1738,18 +1813,24 @@ export class ModelRegistry {
|
|
|
1738
1813
|
return {
|
|
1739
1814
|
baseUrl: override.baseUrl ?? baseOverride?.baseUrl,
|
|
1740
1815
|
apiKey: override.apiKey ?? baseOverride?.apiKey,
|
|
1816
|
+
authHeader: override.authHeader ?? baseOverride?.authHeader,
|
|
1741
1817
|
headers: override.headers ? { ...(baseOverride?.headers ?? {}), ...override.headers } : baseOverride?.headers,
|
|
1742
1818
|
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
1743
1819
|
};
|
|
1744
1820
|
}
|
|
1745
1821
|
#applyProviderTransportOverride<T extends { baseUrl?: string; headers?: Record<string, string> }>(
|
|
1746
1822
|
entry: T,
|
|
1747
|
-
override: Pick<ProviderOverride, "baseUrl" | "headers">,
|
|
1823
|
+
override: Pick<ProviderOverride, "baseUrl" | "headers" | "authHeader" | "apiKey">,
|
|
1748
1824
|
): T {
|
|
1825
|
+
const headers = mergeAuthHeader(
|
|
1826
|
+
override.headers ? { ...entry.headers, ...override.headers } : entry.headers,
|
|
1827
|
+
override.authHeader,
|
|
1828
|
+
override.apiKey,
|
|
1829
|
+
);
|
|
1749
1830
|
return {
|
|
1750
1831
|
...entry,
|
|
1751
1832
|
baseUrl: override.baseUrl ?? entry.baseUrl,
|
|
1752
|
-
headers
|
|
1833
|
+
headers,
|
|
1753
1834
|
};
|
|
1754
1835
|
}
|
|
1755
1836
|
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
@@ -2206,8 +2287,13 @@ export class ModelRegistry {
|
|
|
2206
2287
|
return;
|
|
2207
2288
|
}
|
|
2208
2289
|
|
|
2209
|
-
if (config.baseUrl || config.headers) {
|
|
2210
|
-
const transportOverride = {
|
|
2290
|
+
if (config.baseUrl || config.headers || config.apiKey || config.authHeader !== undefined) {
|
|
2291
|
+
const transportOverride = {
|
|
2292
|
+
baseUrl: config.baseUrl,
|
|
2293
|
+
headers: config.headers,
|
|
2294
|
+
apiKey: config.apiKey,
|
|
2295
|
+
authHeader: config.authHeader,
|
|
2296
|
+
};
|
|
2211
2297
|
const nextRuntimeOverride = this.#mergeProviderOverride(
|
|
2212
2298
|
this.#runtimeProviderOverrides.get(providerName),
|
|
2213
2299
|
transportOverride,
|
|
@@ -1485,6 +1485,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
1485
1485
|
},
|
|
1486
1486
|
},
|
|
1487
1487
|
|
|
1488
|
+
"read.summarize.prose": {
|
|
1489
|
+
type: "boolean",
|
|
1490
|
+
default: false,
|
|
1491
|
+
ui: {
|
|
1492
|
+
tab: "editing",
|
|
1493
|
+
label: "Prose Summaries",
|
|
1494
|
+
description: "Return structural summaries for Markdown and plain text reads",
|
|
1495
|
+
},
|
|
1496
|
+
},
|
|
1497
|
+
|
|
1488
1498
|
"read.summarize.minBodyLines": {
|
|
1489
1499
|
type: "number",
|
|
1490
1500
|
default: 4,
|
|
@@ -1945,6 +1955,30 @@ export const SETTINGS_SCHEMA = {
|
|
|
1945
1955
|
default: 60_000,
|
|
1946
1956
|
},
|
|
1947
1957
|
|
|
1958
|
+
// Tool Discovery
|
|
1959
|
+
"tools.discoveryMode": {
|
|
1960
|
+
type: "enum",
|
|
1961
|
+
values: ["off", "mcp-only", "all"] as const,
|
|
1962
|
+
default: "off",
|
|
1963
|
+
ui: {
|
|
1964
|
+
tab: "tools",
|
|
1965
|
+
label: "Tool Discovery",
|
|
1966
|
+
description:
|
|
1967
|
+
"Hide tools behind a search tool to save tokens. 'mcp-only' hides MCP tools; 'all' hides all non-essential built-ins too.",
|
|
1968
|
+
},
|
|
1969
|
+
},
|
|
1970
|
+
|
|
1971
|
+
"tools.essentialOverride": {
|
|
1972
|
+
type: "array",
|
|
1973
|
+
default: [] as string[],
|
|
1974
|
+
ui: {
|
|
1975
|
+
tab: "tools",
|
|
1976
|
+
label: "Essential Tools Override",
|
|
1977
|
+
description:
|
|
1978
|
+
"Override the always-loaded built-in tools (default: read, bash, edit). Leave empty to use defaults.",
|
|
1979
|
+
},
|
|
1980
|
+
},
|
|
1981
|
+
|
|
1948
1982
|
// MCP
|
|
1949
1983
|
"mcp.enableProjectConfig": {
|
|
1950
1984
|
type: "boolean",
|
|
@@ -1996,6 +2030,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
1996
2030
|
// Tasks
|
|
1997
2031
|
// ────────────────────────────────────────────────────────────────────────
|
|
1998
2032
|
|
|
2033
|
+
// Plan mode
|
|
2034
|
+
"plan.enabled": {
|
|
2035
|
+
type: "boolean",
|
|
2036
|
+
default: true,
|
|
2037
|
+
ui: {
|
|
2038
|
+
tab: "tasks",
|
|
2039
|
+
label: "Plan Mode",
|
|
2040
|
+
description: "Enable plan mode for read-only exploration and planning before execution",
|
|
2041
|
+
},
|
|
2042
|
+
},
|
|
2043
|
+
|
|
1999
2044
|
// Delegation
|
|
2000
2045
|
"task.isolation.mode": {
|
|
2001
2046
|
type: "enum",
|
|
@@ -2268,7 +2313,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
2268
2313
|
{ value: "kagi", label: "Kagi", description: "Requires KAGI_API_KEY and Kagi Search API beta access" },
|
|
2269
2314
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
2270
2315
|
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
2271
|
-
{ value: "searxng", label: "SearXNG", description: "Requires searxng.endpoint" },
|
|
2316
|
+
{ value: "searxng", label: "SearXNG", description: "Requires SEARXNG_ENDPOINT or searxng.endpoint" },
|
|
2272
2317
|
],
|
|
2273
2318
|
},
|
|
2274
2319
|
},
|
package/src/config/settings.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
15
16
|
import * as path from "node:path";
|
|
16
17
|
import {
|
|
17
18
|
getAgentDbPath,
|
|
@@ -98,6 +99,74 @@ function setByPath(obj: RawSettings, segments: string[], value: unknown): void {
|
|
|
98
99
|
current[segments[segments.length - 1]] = value;
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
const PATH_SCOPED_ARRAY_SETTINGS = new Set<SettingPath>(["enabledModels", "disabledProviders"]);
|
|
103
|
+
|
|
104
|
+
type PathScopedStringArrayEntry = {
|
|
105
|
+
path?: unknown;
|
|
106
|
+
paths?: unknown;
|
|
107
|
+
pathPrefix?: unknown;
|
|
108
|
+
pathPrefixes?: unknown;
|
|
109
|
+
values?: unknown;
|
|
110
|
+
items?: unknown;
|
|
111
|
+
models?: unknown;
|
|
112
|
+
providers?: unknown;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function normalizePathPrefix(prefix: string): string {
|
|
116
|
+
const expanded =
|
|
117
|
+
prefix === "~" ? os.homedir() : prefix.startsWith("~/") ? path.join(os.homedir(), prefix.slice(2)) : prefix;
|
|
118
|
+
return path.resolve(expanded);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function pathMatchesPrefix(cwd: string, prefix: string): boolean {
|
|
122
|
+
const relative = path.relative(normalizePathPrefix(prefix), path.resolve(cwd));
|
|
123
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function stringArrayFromUnknown(value: unknown): string[] {
|
|
127
|
+
if (typeof value === "string") return [value];
|
|
128
|
+
if (Array.isArray(value)) return value.filter((item): item is string => typeof item === "string");
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolvePathScopedStringArray(settingPath: SettingPath, value: unknown, cwd: string): string[] | undefined {
|
|
133
|
+
if (!PATH_SCOPED_ARRAY_SETTINGS.has(settingPath) || !Array.isArray(value)) return undefined;
|
|
134
|
+
|
|
135
|
+
const resolved: string[] = [];
|
|
136
|
+
for (const entry of value) {
|
|
137
|
+
if (typeof entry === "string") {
|
|
138
|
+
resolved.push(entry);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
142
|
+
|
|
143
|
+
const scoped = entry as PathScopedStringArrayEntry;
|
|
144
|
+
const prefixes = [
|
|
145
|
+
...stringArrayFromUnknown(scoped.path),
|
|
146
|
+
...stringArrayFromUnknown(scoped.paths),
|
|
147
|
+
...stringArrayFromUnknown(scoped.pathPrefix),
|
|
148
|
+
...stringArrayFromUnknown(scoped.pathPrefixes),
|
|
149
|
+
];
|
|
150
|
+
if (prefixes.length === 0 || !prefixes.some(prefix => pathMatchesPrefix(cwd, prefix))) continue;
|
|
151
|
+
|
|
152
|
+
const values =
|
|
153
|
+
settingPath === "enabledModels"
|
|
154
|
+
? [
|
|
155
|
+
...stringArrayFromUnknown(scoped.values),
|
|
156
|
+
...stringArrayFromUnknown(scoped.items),
|
|
157
|
+
...stringArrayFromUnknown(scoped.models),
|
|
158
|
+
]
|
|
159
|
+
: [
|
|
160
|
+
...stringArrayFromUnknown(scoped.values),
|
|
161
|
+
...stringArrayFromUnknown(scoped.items),
|
|
162
|
+
...stringArrayFromUnknown(scoped.providers),
|
|
163
|
+
];
|
|
164
|
+
resolved.push(...values);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return resolved;
|
|
168
|
+
}
|
|
169
|
+
|
|
101
170
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
102
171
|
// Settings Class
|
|
103
172
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -201,7 +270,8 @@ export class Settings {
|
|
|
201
270
|
const segments = path.split(".");
|
|
202
271
|
const value = getByPath(this.#merged, segments);
|
|
203
272
|
if (value !== undefined) {
|
|
204
|
-
|
|
273
|
+
const pathScopedValue = resolvePathScopedStringArray(path, value, this.#cwd);
|
|
274
|
+
return (pathScopedValue ?? value) as SettingValue<P>;
|
|
205
275
|
}
|
|
206
276
|
return getDefault(path);
|
|
207
277
|
}
|
package/src/dap/client.ts
CHANGED
package/src/discovery/builtin.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { logger, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { YAML } from "bun";
|
|
8
9
|
import { registerProvider } from "../capability";
|
|
9
10
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
10
11
|
import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
|
|
@@ -778,22 +779,46 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
|
778
779
|
const items: Settings[] = [];
|
|
779
780
|
const warnings: string[] = [];
|
|
780
781
|
|
|
782
|
+
const parseYamlSettings = (content: string, filePath: string): Record<string, unknown> | null => {
|
|
783
|
+
try {
|
|
784
|
+
const data = YAML.parse(content);
|
|
785
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return {};
|
|
786
|
+
return data as Record<string, unknown>;
|
|
787
|
+
} catch {
|
|
788
|
+
warnings.push(`Failed to parse ${filePath}`);
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
781
793
|
for (const { dir, level } of await getConfigDirs(ctx)) {
|
|
782
794
|
const settingsPath = path.join(dir, "settings.json");
|
|
783
|
-
const
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
795
|
+
const settingsContent = await readFile(settingsPath);
|
|
796
|
+
if (settingsContent) {
|
|
797
|
+
const data = tryParseJson<Record<string, unknown>>(settingsContent);
|
|
798
|
+
if (data) {
|
|
799
|
+
items.push({
|
|
800
|
+
path: settingsPath,
|
|
801
|
+
data,
|
|
802
|
+
level,
|
|
803
|
+
_source: createSourceMeta(PROVIDER_ID, settingsPath, level),
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
warnings.push(`Failed to parse ${settingsPath}`);
|
|
807
|
+
}
|
|
790
808
|
}
|
|
791
809
|
|
|
810
|
+
const configPath = path.join(dir, "config.yml");
|
|
811
|
+
const configContent = await readFile(configPath);
|
|
812
|
+
if (!configContent) continue;
|
|
813
|
+
|
|
814
|
+
const data = parseYamlSettings(configContent, configPath);
|
|
815
|
+
if (!data) continue;
|
|
816
|
+
|
|
792
817
|
items.push({
|
|
793
|
-
path:
|
|
818
|
+
path: configPath,
|
|
794
819
|
data,
|
|
795
820
|
level,
|
|
796
|
-
_source: createSourceMeta(PROVIDER_ID,
|
|
821
|
+
_source: createSourceMeta(PROVIDER_ID, configPath, level),
|
|
797
822
|
});
|
|
798
823
|
}
|
|
799
824
|
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -811,9 +811,10 @@ export async function listClaudePluginRoots(
|
|
|
811
811
|
|
|
812
812
|
// ── OMP installed plugins registry ───────────────────────────────────────
|
|
813
813
|
// OMP registry is authoritative: its entries replace Claude's entries for the same plugin ID.
|
|
814
|
-
// getPluginsDir() resolves to the
|
|
815
|
-
//
|
|
816
|
-
|
|
814
|
+
// In production `home` is `os.homedir()`, so `getPluginsDir(home)` resolves to the
|
|
815
|
+
// same XDG-aware path the marketplace writer uses (reads and writes always agree).
|
|
816
|
+
// Tests pass a temp dir, which short-circuits the resolver for deterministic isolation.
|
|
817
|
+
const ompRegistryPath = path.join(getPluginsDir(home), "installed_plugins.json");
|
|
817
818
|
const ompContent = await readFile(ompRegistryPath);
|
|
818
819
|
if (ompContent) {
|
|
819
820
|
const ompRegistry = parseClaudePluginsRegistry(ompContent);
|
package/src/edit/index.ts
CHANGED
|
@@ -246,6 +246,7 @@ async function executeSinglePathEntries(
|
|
|
246
246
|
export class EditTool implements AgentTool<TInput> {
|
|
247
247
|
readonly name = "edit";
|
|
248
248
|
readonly label = "Edit";
|
|
249
|
+
readonly loadMode = "essential";
|
|
249
250
|
readonly nonAbortable = true;
|
|
250
251
|
readonly concurrency = "exclusive";
|
|
251
252
|
readonly strict = true;
|