@oh-my-pi/pi-coding-agent 8.1.0 → 8.2.0
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 +21 -1
- package/docs/session.md +111 -46
- package/examples/custom-tools/hello/index.ts +1 -1
- package/examples/custom-tools/todo/index.ts +3 -4
- package/examples/extensions/api-demo.ts +0 -1
- package/examples/extensions/chalk-logger.ts +2 -3
- package/examples/extensions/hello.ts +0 -1
- package/examples/extensions/pirate.ts +0 -1
- package/examples/extensions/plan-mode.ts +15 -16
- package/examples/extensions/todo.ts +3 -4
- package/examples/extensions/tools.ts +1 -2
- package/examples/extensions/with-deps/index.ts +0 -1
- package/examples/hooks/auto-commit-on-exit.ts +1 -2
- package/examples/hooks/confirm-destructive.ts +0 -1
- package/examples/hooks/custom-compaction.ts +1 -2
- package/examples/hooks/dirty-repo-guard.ts +0 -1
- package/examples/hooks/file-trigger.ts +3 -4
- package/examples/hooks/git-checkpoint.ts +0 -1
- package/examples/hooks/handoff.ts +3 -4
- package/examples/hooks/permission-gate.ts +1 -2
- package/examples/hooks/protected-paths.ts +1 -2
- package/examples/hooks/qna.ts +2 -3
- package/examples/hooks/snake.ts +4 -5
- package/examples/hooks/status-line.ts +0 -1
- package/examples/sdk/01-minimal.ts +2 -3
- package/examples/sdk/02-custom-model.ts +2 -3
- package/examples/sdk/03-custom-prompt.ts +3 -4
- package/examples/sdk/04-skills.ts +2 -3
- package/examples/sdk/06-extensions.ts +1 -2
- package/examples/sdk/06-hooks.ts +6 -7
- package/examples/sdk/07-context-files.ts +0 -1
- package/examples/sdk/08-prompt-templates.ts +0 -1
- package/examples/sdk/08-slash-commands.ts +0 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
- package/examples/sdk/10-settings.ts +0 -1
- package/examples/sdk/11-sessions.ts +0 -1
- package/package.json +51 -23
- package/scripts/format-prompts.ts +0 -1
- package/src/capability/context-file.ts +2 -3
- package/src/capability/extension-module.ts +2 -3
- package/src/capability/extension.ts +2 -3
- package/src/capability/fs.ts +20 -21
- package/src/capability/hook.ts +2 -3
- package/src/capability/index.ts +15 -16
- package/src/capability/instruction.ts +2 -3
- package/src/capability/mcp.ts +2 -3
- package/src/capability/prompt.ts +2 -3
- package/src/capability/rule.ts +2 -3
- package/src/capability/settings.ts +1 -2
- package/src/capability/skill.ts +2 -3
- package/src/capability/slash-command.ts +2 -3
- package/src/capability/ssh.ts +2 -3
- package/src/capability/system-prompt.ts +2 -3
- package/src/capability/tool.ts +2 -3
- package/src/cli/args.ts +5 -6
- package/src/cli/config-cli.ts +6 -7
- package/src/cli/file-processor.ts +19 -17
- package/src/cli/jupyter-cli.ts +105 -0
- package/src/cli/list-models.ts +10 -11
- package/src/cli/plugin-cli.ts +20 -21
- package/src/cli/session-picker.ts +2 -3
- package/src/cli/setup-cli.ts +2 -3
- package/src/cli/stats-cli.ts +2 -3
- package/src/cli/update-cli.ts +25 -22
- package/src/commit/agentic/agent.ts +21 -23
- package/src/commit/agentic/fallback.ts +9 -9
- package/src/commit/agentic/index.ts +30 -38
- package/src/commit/agentic/state.ts +1 -6
- package/src/commit/agentic/tools/analyze-file.ts +15 -15
- package/src/commit/agentic/tools/git-file-diff.ts +3 -3
- package/src/commit/agentic/tools/git-hunk.ts +7 -7
- package/src/commit/agentic/tools/git-overview.ts +5 -5
- package/src/commit/agentic/tools/index.ts +14 -14
- package/src/commit/agentic/tools/propose-changelog.ts +6 -6
- package/src/commit/agentic/tools/propose-commit.ts +8 -8
- package/src/commit/agentic/tools/recent-commits.ts +2 -2
- package/src/commit/agentic/tools/split-commit.ts +19 -23
- package/src/commit/agentic/topo-sort.ts +1 -1
- package/src/commit/agentic/trivial.ts +3 -3
- package/src/commit/agentic/validation.ts +12 -12
- package/src/commit/analysis/conventional.ts +7 -11
- package/src/commit/analysis/index.ts +4 -4
- package/src/commit/analysis/scope.ts +4 -4
- package/src/commit/analysis/summary.ts +7 -9
- package/src/commit/analysis/validation.ts +1 -1
- package/src/commit/changelog/detect.ts +6 -6
- package/src/commit/changelog/generate.ts +7 -9
- package/src/commit/changelog/index.ts +13 -13
- package/src/commit/changelog/parse.ts +2 -2
- package/src/commit/cli.ts +1 -1
- package/src/commit/git/diff.ts +3 -3
- package/src/commit/git/index.ts +19 -24
- package/src/commit/index.ts +1 -1
- package/src/commit/map-reduce/index.ts +9 -9
- package/src/commit/map-reduce/map-phase.ts +19 -34
- package/src/commit/map-reduce/reduce-phase.ts +9 -11
- package/src/commit/message.ts +2 -2
- package/src/commit/model-selection.ts +3 -7
- package/src/commit/pipeline.ts +20 -22
- package/src/commit/utils/exclusions.ts +3 -3
- package/src/config/file-lock.ts +17 -7
- package/src/config/keybindings.ts +6 -8
- package/src/config/model-registry.ts +55 -37
- package/src/config/model-resolver.ts +18 -19
- package/src/config/prompt-templates.ts +11 -11
- package/src/config/settings-manager.ts +50 -34
- package/src/config.ts +60 -62
- package/src/cursor.ts +11 -9
- package/src/discovery/agents-md.ts +11 -12
- package/src/discovery/builtin.ts +68 -73
- package/src/discovery/claude.ts +41 -42
- package/src/discovery/cline.ts +11 -12
- package/src/discovery/codex.ts +52 -53
- package/src/discovery/cursor.ts +9 -10
- package/src/discovery/gemini.ts +17 -22
- package/src/discovery/github.ts +13 -14
- package/src/discovery/helpers.ts +35 -34
- package/src/discovery/index.ts +16 -18
- package/src/discovery/mcp-json.ts +8 -9
- package/src/discovery/ssh.ts +8 -9
- package/src/discovery/vscode.ts +4 -5
- package/src/discovery/windsurf.ts +6 -7
- package/src/exa/company.ts +1 -2
- package/src/exa/index.ts +2 -3
- package/src/exa/linkedin.ts +1 -2
- package/src/exa/mcp-client.ts +14 -16
- package/src/exa/render.ts +10 -11
- package/src/exa/researcher.ts +1 -2
- package/src/exa/search.ts +1 -2
- package/src/exa/types.ts +0 -1
- package/src/exa/websets.ts +1 -2
- package/src/exec/bash-executor.ts +3 -4
- package/src/exec/exec.ts +0 -1
- package/src/export/custom-share.ts +5 -6
- package/src/export/html/index.ts +24 -21
- package/src/export/ttsr.ts +2 -3
- package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
- package/src/extensibility/custom-commands/loader.ts +17 -14
- package/src/extensibility/custom-commands/types.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +10 -11
- package/src/extensibility/custom-tools/types.ts +6 -7
- package/src/extensibility/custom-tools/wrapper.ts +2 -3
- package/src/extensibility/extensions/loader.ts +75 -53
- package/src/extensibility/extensions/runner.ts +11 -12
- package/src/extensibility/extensions/types.ts +19 -26
- package/src/extensibility/extensions/wrapper.ts +3 -4
- package/src/extensibility/hooks/index.ts +1 -1
- package/src/extensibility/hooks/loader.ts +8 -9
- package/src/extensibility/hooks/runner.ts +7 -8
- package/src/extensibility/hooks/tool-wrapper.ts +0 -1
- package/src/extensibility/hooks/types.ts +10 -17
- package/src/extensibility/plugins/doctor.ts +3 -3
- package/src/extensibility/plugins/installer.ts +27 -27
- package/src/extensibility/plugins/loader.ts +59 -56
- package/src/extensibility/plugins/manager.ts +211 -171
- package/src/extensibility/plugins/parser.ts +1 -1
- package/src/extensibility/plugins/paths.ts +8 -8
- package/src/extensibility/skills.ts +63 -60
- package/src/extensibility/slash-commands.ts +10 -10
- package/src/index.ts +46 -46
- package/src/internal-urls/agent-protocol.ts +21 -11
- package/src/internal-urls/artifact-protocol.ts +17 -13
- package/src/internal-urls/router.ts +1 -2
- package/src/internal-urls/rule-protocol.ts +3 -4
- package/src/internal-urls/skill-protocol.ts +3 -4
- package/src/ipy/executor.ts +14 -10
- package/src/ipy/gateway-coordinator.ts +79 -90
- package/src/ipy/kernel.ts +32 -30
- package/src/ipy/modules.ts +13 -13
- package/src/lsp/client.ts +21 -10
- package/src/lsp/clients/biome-client.ts +1 -2
- package/src/lsp/clients/index.ts +3 -3
- package/src/lsp/clients/lsp-linter-client.ts +4 -5
- package/src/lsp/config.ts +15 -15
- package/src/lsp/edits.ts +4 -5
- package/src/lsp/index.ts +43 -44
- package/src/lsp/lspmux.ts +8 -8
- package/src/lsp/render.ts +10 -16
- package/src/lsp/utils.ts +3 -3
- package/src/main.ts +55 -34
- package/src/mcp/client.ts +2 -3
- package/src/mcp/config.ts +5 -6
- package/src/mcp/json-rpc.ts +0 -1
- package/src/mcp/loader.ts +3 -4
- package/src/mcp/manager.ts +17 -18
- package/src/mcp/tool-bridge.ts +4 -9
- package/src/mcp/tool-cache.ts +2 -3
- package/src/mcp/transports/http.ts +2 -4
- package/src/mcp/transports/stdio.ts +1 -2
- package/src/migrations.ts +60 -49
- package/src/modes/components/armin.ts +4 -5
- package/src/modes/components/assistant-message.ts +6 -6
- package/src/modes/components/bash-execution.ts +7 -8
- package/src/modes/components/bordered-loader.ts +3 -3
- package/src/modes/components/branch-summary-message.ts +3 -3
- package/src/modes/components/compaction-summary-message.ts +3 -3
- package/src/modes/components/countdown-timer.ts +0 -1
- package/src/modes/components/custom-message.ts +5 -5
- package/src/modes/components/diff.ts +1 -1
- package/src/modes/components/dynamic-border.ts +2 -2
- package/src/modes/components/extensions/extension-dashboard.ts +6 -7
- package/src/modes/components/extensions/extension-list.ts +2 -3
- package/src/modes/components/extensions/inspector-panel.ts +3 -4
- package/src/modes/components/extensions/state-manager.ts +25 -26
- package/src/modes/components/extensions/types.ts +1 -2
- package/src/modes/components/footer.ts +47 -43
- package/src/modes/components/history-search.ts +2 -2
- package/src/modes/components/hook-editor.ts +3 -4
- package/src/modes/components/hook-input.ts +2 -3
- package/src/modes/components/hook-message.ts +5 -5
- package/src/modes/components/hook-selector.ts +2 -3
- package/src/modes/components/keybinding-hints.ts +2 -3
- package/src/modes/components/login-dialog.ts +2 -2
- package/src/modes/components/model-selector.ts +12 -12
- package/src/modes/components/oauth-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +20 -20
- package/src/modes/components/python-execution.ts +7 -8
- package/src/modes/components/queue-mode-selector.ts +3 -3
- package/src/modes/components/read-tool-group.ts +2 -2
- package/src/modes/components/session-selector.ts +4 -4
- package/src/modes/components/settings-defs.ts +77 -69
- package/src/modes/components/settings-selector.ts +16 -16
- package/src/modes/components/show-images-selector.ts +2 -2
- package/src/modes/components/status-line/segments.ts +4 -4
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line/types.ts +2 -2
- package/src/modes/components/status-line-segment-editor.ts +7 -8
- package/src/modes/components/status-line.ts +12 -12
- package/src/modes/components/theme-selector.ts +8 -7
- package/src/modes/components/thinking-selector.ts +4 -4
- package/src/modes/components/todo-display.ts +2 -2
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +11 -16
- package/src/modes/components/tree-selector.ts +11 -11
- package/src/modes/components/ttsr-notification.ts +5 -5
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +1 -1
- package/src/modes/components/visual-truncate.ts +0 -1
- package/src/modes/components/welcome.ts +4 -4
- package/src/modes/controllers/command-controller.ts +46 -47
- package/src/modes/controllers/event-controller.ts +16 -20
- package/src/modes/controllers/extension-ui-controller.ts +40 -46
- package/src/modes/controllers/input-controller.ts +17 -18
- package/src/modes/controllers/selector-controller.ts +103 -91
- package/src/modes/index.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -29
- package/src/modes/print-mode.ts +12 -13
- package/src/modes/rpc/rpc-client.ts +7 -8
- package/src/modes/rpc/rpc-mode.ts +24 -25
- package/src/modes/rpc/rpc-types.ts +3 -4
- package/src/modes/theme/mermaid-cache.ts +2 -2
- package/src/modes/theme/theme.ts +128 -53
- package/src/modes/types.ts +10 -10
- package/src/modes/utils/ui-helpers.ts +17 -17
- package/src/patch/applicator.ts +18 -19
- package/src/patch/diff.ts +1 -2
- package/src/patch/fuzzy.ts +1 -2
- package/src/patch/index.ts +10 -11
- package/src/patch/normalize.ts +4 -4
- package/src/patch/normative.ts +1 -2
- package/src/patch/parser.ts +8 -9
- package/src/patch/shared.ts +12 -13
- package/src/sdk.ts +60 -63
- package/src/session/agent-session.ts +83 -84
- package/src/session/agent-storage.ts +11 -11
- package/src/session/artifacts.ts +8 -9
- package/src/session/auth-storage.ts +25 -29
- package/src/session/compaction/branch-summarization.ts +7 -10
- package/src/session/compaction/compaction.ts +8 -19
- package/src/session/compaction/utils.ts +6 -9
- package/src/session/history-storage.ts +10 -10
- package/src/session/messages.ts +4 -5
- package/src/session/session-manager.ts +76 -65
- package/src/session/session-storage.ts +57 -69
- package/src/session/storage-migration.ts +2 -3
- package/src/session/streaming-output.ts +2 -2
- package/src/ssh/connection-manager.ts +43 -50
- package/src/ssh/ssh-executor.ts +2 -2
- package/src/ssh/sshfs-mount.ts +11 -18
- package/src/system-prompt.ts +27 -34
- package/src/task/agents.ts +45 -30
- package/src/task/commands.ts +6 -7
- package/src/task/discovery.ts +39 -76
- package/src/task/executor.ts +14 -15
- package/src/task/index.ts +33 -36
- package/src/task/output-manager.ts +3 -4
- package/src/task/parallel.ts +0 -1
- package/src/task/render.ts +19 -20
- package/src/task/subprocess-tool-registry.ts +1 -2
- package/src/task/worker-protocol.ts +3 -3
- package/src/task/worker.ts +32 -38
- package/src/task/worktree.ts +19 -19
- package/src/tools/ask.ts +8 -9
- package/src/tools/bash-interceptor.ts +1 -5
- package/src/tools/bash.ts +19 -18
- package/src/tools/calculator.ts +12 -12
- package/src/tools/complete.ts +3 -4
- package/src/tools/context.ts +2 -2
- package/src/tools/fetch.ts +23 -26
- package/src/tools/find.ts +15 -16
- package/src/tools/gemini-image.ts +14 -14
- package/src/tools/grep.ts +27 -27
- package/src/tools/index.ts +78 -56
- package/src/tools/list-limit.ts +1 -1
- package/src/tools/ls.ts +7 -7
- package/src/tools/notebook.ts +5 -5
- package/src/tools/output-meta.ts +3 -4
- package/src/tools/output-utils.ts +1 -1
- package/src/tools/path-utils.ts +5 -5
- package/src/tools/python.ts +36 -37
- package/src/tools/read.ts +23 -23
- package/src/tools/render-utils.ts +8 -9
- package/src/tools/renderers.ts +6 -7
- package/src/tools/review.ts +8 -11
- package/src/tools/ssh.ts +31 -30
- package/src/tools/todo-write.ts +13 -13
- package/src/tools/tool-errors.ts +3 -3
- package/src/tools/tool-result.ts +3 -8
- package/src/tools/write.ts +11 -16
- package/src/tui/code-cell.ts +3 -9
- package/src/tui/file-list.ts +3 -4
- package/src/tui/output-block.ts +1 -2
- package/src/tui/status-line.ts +2 -3
- package/src/tui/tree-list.ts +2 -3
- package/src/tui/types.ts +1 -2
- package/src/tui/utils.ts +2 -3
- package/src/utils/changelog.ts +9 -10
- package/src/utils/clipboard.ts +11 -11
- package/src/utils/file-mentions.ts +4 -10
- package/src/utils/frontmatter.ts +6 -3
- package/src/utils/fuzzy.ts +2 -2
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/mime.ts +2 -2
- package/src/utils/shell-snapshot.ts +11 -13
- package/src/utils/shell.ts +4 -5
- package/src/utils/title-generator.ts +8 -9
- package/src/utils/tools-manager.ts +23 -23
- package/src/vendor/photon/index.js +1099 -1059
- package/src/vendor/photon/photon_rs_bg.wasm +0 -0
- package/src/web/scrapers/artifacthub.ts +1 -1
- package/src/web/scrapers/arxiv.ts +2 -2
- package/src/web/scrapers/bluesky.ts +2 -2
- package/src/web/scrapers/cheatsh.ts +1 -1
- package/src/web/scrapers/chocolatey.ts +2 -2
- package/src/web/scrapers/choosealicense.ts +5 -5
- package/src/web/scrapers/cisa-kev.ts +1 -1
- package/src/web/scrapers/crossref.ts +2 -2
- package/src/web/scrapers/devto.ts +3 -3
- package/src/web/scrapers/discogs.ts +3 -4
- package/src/web/scrapers/discourse.ts +1 -1
- package/src/web/scrapers/dockerhub.ts +1 -1
- package/src/web/scrapers/fdroid.ts +2 -2
- package/src/web/scrapers/firefox-addons.ts +3 -3
- package/src/web/scrapers/flathub.ts +1 -1
- package/src/web/scrapers/github.ts +3 -3
- package/src/web/scrapers/gitlab.ts +4 -4
- package/src/web/scrapers/hackernews.ts +2 -2
- package/src/web/scrapers/huggingface.ts +1 -1
- package/src/web/scrapers/iacr.ts +2 -2
- package/src/web/scrapers/index.ts +0 -1
- package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
- package/src/web/scrapers/lemmy.ts +2 -2
- package/src/web/scrapers/maven.ts +2 -2
- package/src/web/scrapers/mdn.ts +2 -4
- package/src/web/scrapers/metacpan.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/npm.ts +1 -1
- package/src/web/scrapers/nuget.ts +2 -2
- package/src/web/scrapers/nvd.ts +3 -3
- package/src/web/scrapers/ollama.ts +7 -9
- package/src/web/scrapers/opencorporates.ts +2 -2
- package/src/web/scrapers/openlibrary.ts +6 -6
- package/src/web/scrapers/orcid.ts +0 -1
- package/src/web/scrapers/osv.ts +2 -2
- package/src/web/scrapers/packagist.ts +1 -1
- package/src/web/scrapers/pubmed.ts +1 -2
- package/src/web/scrapers/rawg.ts +2 -2
- package/src/web/scrapers/readthedocs.ts +1 -2
- package/src/web/scrapers/repology.ts +2 -2
- package/src/web/scrapers/rfc.ts +1 -1
- package/src/web/scrapers/searchcode.ts +2 -2
- package/src/web/scrapers/semantic-scholar.ts +1 -1
- package/src/web/scrapers/snapcraft.ts +2 -2
- package/src/web/scrapers/sourcegraph.ts +1 -1
- package/src/web/scrapers/spdx.ts +3 -3
- package/src/web/scrapers/spotify.ts +0 -1
- package/src/web/scrapers/twitter.ts +1 -1
- package/src/web/scrapers/types.ts +1 -2
- package/src/web/scrapers/utils.ts +5 -5
- package/src/web/scrapers/wikidata.ts +3 -3
- package/src/web/scrapers/youtube.ts +9 -14
- package/src/web/search/auth.ts +4 -9
- package/src/web/search/index.ts +11 -21
- package/src/web/search/providers/anthropic.ts +3 -9
- package/src/web/search/providers/exa.ts +6 -10
- package/src/web/search/providers/perplexity.ts +5 -5
- package/src/web/search/render.ts +16 -18
- package/scripts/generate-wasm-b64.ts +0 -24
- package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
- package/src/task/.executor.ts.kate-swp +0 -0
- package/src/vendor/photon/photon_rs_bg.wasm.b64.js +0 -1
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getAgentDir } from "@oh-my-pi/pi-coding-agent/config";
|
|
1
|
+
import * as path from "node:path";
|
|
4
2
|
import {
|
|
5
3
|
DEFAULT_EDITOR_KEYBINDINGS,
|
|
6
4
|
type EditorAction,
|
|
@@ -11,6 +9,7 @@ import {
|
|
|
11
9
|
setEditorKeybindings,
|
|
12
10
|
} from "@oh-my-pi/pi-tui";
|
|
13
11
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { getAgentDir } from "../config";
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Application-level actions (coding agent specific).
|
|
@@ -162,7 +161,7 @@ export class KeybindingsManager {
|
|
|
162
161
|
* Create from config file and set up editor keybindings.
|
|
163
162
|
*/
|
|
164
163
|
static async create(agentDir: string = getAgentDir()): Promise<KeybindingsManager> {
|
|
165
|
-
const configPath = join(agentDir, "keybindings.json");
|
|
164
|
+
const configPath = path.join(agentDir, "keybindings.json");
|
|
166
165
|
const config = await KeybindingsManager.loadFromFile(configPath);
|
|
167
166
|
const manager = new KeybindingsManager(config);
|
|
168
167
|
|
|
@@ -186,9 +185,8 @@ export class KeybindingsManager {
|
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
private static async loadFromFile(path: string): Promise<KeybindingsConfig> {
|
|
189
|
-
if (!existsSync(path)) return {};
|
|
190
188
|
try {
|
|
191
|
-
return
|
|
189
|
+
return await Bun.file(path).json();
|
|
192
190
|
} catch (error) {
|
|
193
191
|
logger.warn("Failed to parse keybindings config", { path, error: String(error) });
|
|
194
192
|
return {};
|
|
@@ -203,7 +201,7 @@ export class KeybindingsManager {
|
|
|
203
201
|
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
204
202
|
this.appActionToKeys.set(
|
|
205
203
|
action as AppAction,
|
|
206
|
-
keyArray.map(
|
|
204
|
+
keyArray.map(key => normalizeKeyId(key as KeyId)),
|
|
207
205
|
);
|
|
208
206
|
}
|
|
209
207
|
|
|
@@ -213,7 +211,7 @@ export class KeybindingsManager {
|
|
|
213
211
|
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
214
212
|
this.appActionToKeys.set(
|
|
215
213
|
action,
|
|
216
|
-
keyArray.map(
|
|
214
|
+
keyArray.map(key => normalizeKeyId(key as KeyId)),
|
|
217
215
|
);
|
|
218
216
|
}
|
|
219
217
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Model registry - manages built-in and custom models, provides API key resolution.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import { extname } from "node:path";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
7
6
|
import {
|
|
8
7
|
type Api,
|
|
9
8
|
getGitHubCopilotBaseUrl,
|
|
@@ -12,11 +11,11 @@ import {
|
|
|
12
11
|
type Model,
|
|
13
12
|
normalizeDomain,
|
|
14
13
|
} from "@oh-my-pi/pi-ai";
|
|
15
|
-
import
|
|
16
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
14
|
+
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
17
15
|
import { type Static, Type } from "@sinclair/typebox";
|
|
18
16
|
import AjvModule from "ajv";
|
|
19
17
|
import { YAML } from "bun";
|
|
18
|
+
import type { AuthStorage } from "../session/auth-storage";
|
|
20
19
|
|
|
21
20
|
const Ajv = (AjvModule as any).default || AjvModule;
|
|
22
21
|
|
|
@@ -104,10 +103,12 @@ interface CustomModelsResult {
|
|
|
104
103
|
/** Providers with only baseUrl/headers override (no custom models) */
|
|
105
104
|
overrides: Map<string, ProviderOverride>;
|
|
106
105
|
error: string | undefined;
|
|
106
|
+
/** Whether the file was found (true) or didn't exist (false) */
|
|
107
|
+
found: boolean;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
function emptyCustomModelsResult(error?: string): CustomModelsResult {
|
|
110
|
-
return { models: [], replacedProviders: new Set(), overrides: new Map(), error };
|
|
111
|
+
return { models: [], replacedProviders: new Set(), overrides: new Map(), error, found: false };
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
/**
|
|
@@ -139,7 +140,7 @@ export class ModelRegistry {
|
|
|
139
140
|
private fallbackPaths: string[] = [],
|
|
140
141
|
) {
|
|
141
142
|
// Set up fallback resolver for custom provider API keys
|
|
142
|
-
this.authStorage.setFallbackResolver(
|
|
143
|
+
this.authStorage.setFallbackResolver(provider => {
|
|
143
144
|
const keyConfig = this.customProviderApiKeys.get(provider);
|
|
144
145
|
if (keyConfig) {
|
|
145
146
|
return resolveApiKeyConfig(keyConfig);
|
|
@@ -161,7 +162,7 @@ export class ModelRegistry {
|
|
|
161
162
|
instance.customProviderApiKeys = new Map(Object.entries(data.customProviderApiKeys ?? {}));
|
|
162
163
|
instance.loadError = data.loadError;
|
|
163
164
|
|
|
164
|
-
authStorage.setFallbackResolver(
|
|
165
|
+
authStorage.setFallbackResolver(provider => {
|
|
165
166
|
const keyConfig = instance.customProviderApiKeys.get(provider);
|
|
166
167
|
if (keyConfig) {
|
|
167
168
|
return resolveApiKeyConfig(keyConfig);
|
|
@@ -203,7 +204,7 @@ export class ModelRegistry {
|
|
|
203
204
|
return this.loadError;
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
private loadModels()
|
|
207
|
+
private loadModels() {
|
|
207
208
|
// Load custom models from models.json first (to know which providers to skip/override)
|
|
208
209
|
let customModels: Model<Api>[] = [];
|
|
209
210
|
let replacedProviders: Set<string> = new Set();
|
|
@@ -215,19 +216,20 @@ export class ModelRegistry {
|
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
for (const modelsPath of pathsToCheck) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
219
|
+
const result = this.loadCustomModels(modelsPath);
|
|
220
|
+
if (!result.found) {
|
|
221
|
+
continue; // File doesn't exist, try next path
|
|
222
|
+
}
|
|
223
|
+
logger.debug("ModelRegistry.loadModels loading", { path: modelsPath });
|
|
224
|
+
if (result.error) {
|
|
225
|
+
this.loadError = result.error;
|
|
226
|
+
// Keep built-in models even if custom models failed to load
|
|
227
|
+
} else {
|
|
228
|
+
customModels = result.models;
|
|
229
|
+
replacedProviders = result.replacedProviders;
|
|
230
|
+
overrides = result.overrides;
|
|
230
231
|
}
|
|
232
|
+
break; // Use first existing file
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
const builtInModels = this.loadBuiltInModels(replacedProviders, overrides);
|
|
@@ -240,7 +242,7 @@ export class ModelRegistry {
|
|
|
240
242
|
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
|
|
241
243
|
: undefined;
|
|
242
244
|
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
|
|
243
|
-
this.models = combined.map(
|
|
245
|
+
this.models = combined.map(m => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
|
244
246
|
} else {
|
|
245
247
|
this.models = combined;
|
|
246
248
|
}
|
|
@@ -249,14 +251,14 @@ export class ModelRegistry {
|
|
|
249
251
|
/** Load built-in models, skipping replaced providers and applying overrides */
|
|
250
252
|
private loadBuiltInModels(replacedProviders: Set<string>, overrides: Map<string, ProviderOverride>): Model<Api>[] {
|
|
251
253
|
return getProviders()
|
|
252
|
-
.filter(
|
|
253
|
-
.flatMap(
|
|
254
|
+
.filter(provider => !replacedProviders.has(provider))
|
|
255
|
+
.flatMap(provider => {
|
|
254
256
|
const models = getModels(provider as any) as Model<Api>[];
|
|
255
257
|
const override = overrides.get(provider);
|
|
256
258
|
if (!override) return models;
|
|
257
259
|
|
|
258
260
|
// Apply baseUrl/headers override to all models of this provider
|
|
259
|
-
return models.map(
|
|
261
|
+
return models.map(m => ({
|
|
260
262
|
...m,
|
|
261
263
|
baseUrl: override.baseUrl ?? m.baseUrl,
|
|
262
264
|
headers: override.headers ? { ...m.headers, ...override.headers } : m.headers,
|
|
@@ -265,13 +267,23 @@ export class ModelRegistry {
|
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
private loadCustomModels(modelsPath: string): CustomModelsResult {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
let content: string;
|
|
271
|
+
try {
|
|
272
|
+
content = fs.readFileSync(modelsPath, "utf-8");
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (isEnoent(error)) {
|
|
275
|
+
return emptyCustomModelsResult();
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
...emptyCustomModelsResult(
|
|
279
|
+
`Failed to load models config: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsPath}`,
|
|
280
|
+
),
|
|
281
|
+
found: true,
|
|
282
|
+
};
|
|
270
283
|
}
|
|
271
284
|
|
|
272
285
|
try {
|
|
273
|
-
const
|
|
274
|
-
const ext = extname(modelsPath).toLowerCase();
|
|
286
|
+
const ext = path.extname(modelsPath).toLowerCase();
|
|
275
287
|
let config: ModelsConfig;
|
|
276
288
|
|
|
277
289
|
if (ext === ".yaml" || ext === ".yml") {
|
|
@@ -315,14 +327,20 @@ export class ModelRegistry {
|
|
|
315
327
|
}
|
|
316
328
|
}
|
|
317
329
|
|
|
318
|
-
return { models: this.parseModels(config), replacedProviders, overrides, error: undefined };
|
|
330
|
+
return { models: this.parseModels(config), replacedProviders, overrides, error: undefined, found: true };
|
|
319
331
|
} catch (error) {
|
|
320
332
|
if (error instanceof SyntaxError) {
|
|
321
|
-
return
|
|
333
|
+
return {
|
|
334
|
+
...emptyCustomModelsResult(`Failed to parse models config: ${error.message}\n\nFile: ${modelsPath}`),
|
|
335
|
+
found: true,
|
|
336
|
+
};
|
|
322
337
|
}
|
|
323
|
-
return
|
|
324
|
-
|
|
325
|
-
|
|
338
|
+
return {
|
|
339
|
+
...emptyCustomModelsResult(
|
|
340
|
+
`Failed to load models config: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsPath}`,
|
|
341
|
+
),
|
|
342
|
+
found: true,
|
|
343
|
+
};
|
|
326
344
|
}
|
|
327
345
|
}
|
|
328
346
|
|
|
@@ -431,21 +449,21 @@ export class ModelRegistry {
|
|
|
431
449
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
432
450
|
*/
|
|
433
451
|
getAvailable(): Model<Api>[] {
|
|
434
|
-
return this.models.filter(
|
|
452
|
+
return this.models.filter(m => this.authStorage.hasAuth(m.provider));
|
|
435
453
|
}
|
|
436
454
|
|
|
437
455
|
/**
|
|
438
456
|
* Find a model by provider and ID.
|
|
439
457
|
*/
|
|
440
458
|
find(provider: string, modelId: string): Model<Api> | undefined {
|
|
441
|
-
return this.models.find(
|
|
459
|
+
return this.models.find(m => m.provider === provider && m.id === modelId);
|
|
442
460
|
}
|
|
443
461
|
|
|
444
462
|
/**
|
|
445
463
|
* Get the base URL associated with a provider, if any model defines one.
|
|
446
464
|
*/
|
|
447
465
|
getProviderBaseUrl(provider: string): string | undefined {
|
|
448
|
-
return this.models.find(
|
|
466
|
+
return this.models.find(m => m.provider === provider && m.baseUrl)?.baseUrl;
|
|
449
467
|
}
|
|
450
468
|
|
|
451
469
|
/**
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Model resolution, scoping, and initial selection
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
4
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
6
5
|
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
|
7
|
-
import { isValidThinkingLevel } from "@oh-my-pi/pi-coding-agent/cli/args";
|
|
8
6
|
import chalk from "chalk";
|
|
7
|
+
import { isValidThinkingLevel } from "../cli/args";
|
|
9
8
|
import type { ModelRegistry } from "./model-registry";
|
|
10
9
|
|
|
11
10
|
/** Default model IDs for each known provider */
|
|
@@ -84,7 +83,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
|
|
84
83
|
const provider = modelPattern.substring(0, slashIndex);
|
|
85
84
|
const modelId = modelPattern.substring(slashIndex + 1);
|
|
86
85
|
const providerMatch = availableModels.find(
|
|
87
|
-
|
|
86
|
+
m => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
|
|
88
87
|
);
|
|
89
88
|
if (providerMatch) {
|
|
90
89
|
return providerMatch;
|
|
@@ -93,14 +92,14 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
// Check for exact ID match (case-insensitive)
|
|
96
|
-
const exactMatch = availableModels.find(
|
|
95
|
+
const exactMatch = availableModels.find(m => m.id.toLowerCase() === modelPattern.toLowerCase());
|
|
97
96
|
if (exactMatch) {
|
|
98
97
|
return exactMatch;
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
// No exact match - fall back to partial matching
|
|
102
101
|
const matches = availableModels.filter(
|
|
103
|
-
|
|
102
|
+
m =>
|
|
104
103
|
m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
|
|
105
104
|
m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
|
|
106
105
|
);
|
|
@@ -110,8 +109,8 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
// Separate into aliases and dated versions
|
|
113
|
-
const aliases = matches.filter(
|
|
114
|
-
const datedVersions = matches.filter(
|
|
112
|
+
const aliases = matches.filter(m => isAlias(m.id));
|
|
113
|
+
const datedVersions = matches.filter(m => !isAlias(m.id));
|
|
115
114
|
|
|
116
115
|
if (aliases.length > 0) {
|
|
117
116
|
// Prefer alias - if multiple aliases, pick the one that sorts highest
|
|
@@ -226,7 +225,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
|
|
|
226
225
|
|
|
227
226
|
// Match against "provider/modelId" format OR just model ID
|
|
228
227
|
// This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
|
|
229
|
-
const matchingModels = availableModels.filter(
|
|
228
|
+
const matchingModels = availableModels.filter(m => {
|
|
230
229
|
const fullId = `${m.provider}/${m.id}`;
|
|
231
230
|
const glob = new Bun.Glob(globPattern.toLowerCase());
|
|
232
231
|
return glob.match(fullId.toLowerCase()) || glob.match(m.id.toLowerCase());
|
|
@@ -238,7 +237,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
|
|
|
238
237
|
}
|
|
239
238
|
|
|
240
239
|
for (const model of matchingModels) {
|
|
241
|
-
if (!scopedModels.find(
|
|
240
|
+
if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
|
|
242
241
|
scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
|
|
243
242
|
}
|
|
244
243
|
}
|
|
@@ -257,7 +256,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
|
|
|
257
256
|
}
|
|
258
257
|
|
|
259
258
|
// Avoid duplicates
|
|
260
|
-
if (!scopedModels.find(
|
|
259
|
+
if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
|
|
261
260
|
scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
|
|
262
261
|
}
|
|
263
262
|
}
|
|
@@ -343,7 +342,7 @@ export async function findInitialModel(options: {
|
|
|
343
342
|
// Try to find a default model from known providers
|
|
344
343
|
for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
|
|
345
344
|
const defaultId = defaultModelPerProvider[provider];
|
|
346
|
-
const match = availableModels.find(
|
|
345
|
+
const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
|
|
347
346
|
if (match) {
|
|
348
347
|
return { model: match, thinkingLevel: "off", fallbackMessage: undefined };
|
|
349
348
|
}
|
|
@@ -405,7 +404,7 @@ export async function restoreModelFromSession(
|
|
|
405
404
|
let fallbackModel: Model<Api> | undefined;
|
|
406
405
|
for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
|
|
407
406
|
const defaultId = defaultModelPerProvider[provider];
|
|
408
|
-
const match = availableModels.find(
|
|
407
|
+
const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
|
|
409
408
|
if (match) {
|
|
410
409
|
fallbackModel = match;
|
|
411
410
|
break;
|
|
@@ -450,7 +449,7 @@ export async function findSmolModel(
|
|
|
450
449
|
if (savedModel) {
|
|
451
450
|
const parsed = parseModelString(savedModel);
|
|
452
451
|
if (parsed) {
|
|
453
|
-
const match = availableModels.find(
|
|
452
|
+
const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
454
453
|
if (match) return match;
|
|
455
454
|
}
|
|
456
455
|
}
|
|
@@ -458,15 +457,15 @@ export async function findSmolModel(
|
|
|
458
457
|
// 2. Try priority chain
|
|
459
458
|
for (const pattern of SMOL_MODEL_PRIORITY) {
|
|
460
459
|
// Try exact match with provider prefix
|
|
461
|
-
const providerMatch = availableModels.find(
|
|
460
|
+
const providerMatch = availableModels.find(m => `${m.provider}/${m.id}`.toLowerCase() === pattern);
|
|
462
461
|
if (providerMatch) return providerMatch;
|
|
463
462
|
|
|
464
463
|
// Try exact match first
|
|
465
|
-
const exactMatch = availableModels.find(
|
|
464
|
+
const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern);
|
|
466
465
|
if (exactMatch) return exactMatch;
|
|
467
466
|
|
|
468
467
|
// Try fuzzy match (substring)
|
|
469
|
-
const fuzzyMatch = availableModels.find(
|
|
468
|
+
const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern));
|
|
470
469
|
if (fuzzyMatch) return fuzzyMatch;
|
|
471
470
|
}
|
|
472
471
|
|
|
@@ -493,7 +492,7 @@ export async function findSlowModel(
|
|
|
493
492
|
if (savedModel) {
|
|
494
493
|
const parsed = parseModelString(savedModel);
|
|
495
494
|
if (parsed) {
|
|
496
|
-
const match = availableModels.find(
|
|
495
|
+
const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
497
496
|
if (match) return match;
|
|
498
497
|
}
|
|
499
498
|
}
|
|
@@ -501,11 +500,11 @@ export async function findSlowModel(
|
|
|
501
500
|
// 2. Try priority chain
|
|
502
501
|
for (const pattern of SLOW_MODEL_PRIORITY) {
|
|
503
502
|
// Try exact match first
|
|
504
|
-
const exactMatch = availableModels.find(
|
|
503
|
+
const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern.toLowerCase());
|
|
505
504
|
if (exactMatch) return exactMatch;
|
|
506
505
|
|
|
507
506
|
// Try fuzzy match (substring)
|
|
508
|
-
const fuzzyMatch = availableModels.find(
|
|
507
|
+
const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern.toLowerCase()));
|
|
509
508
|
if (fuzzyMatch) return fuzzyMatch;
|
|
510
509
|
}
|
|
511
510
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { CONFIG_DIR_NAME, getPromptsDir } from "@oh-my-pi/pi-coding-agent/config";
|
|
3
|
-
import { parseFrontmatter } from "@oh-my-pi/pi-coding-agent/utils/frontmatter";
|
|
1
|
+
import * as path from "node:path";
|
|
4
2
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
5
3
|
import Handlebars from "handlebars";
|
|
4
|
+
import { CONFIG_DIR_NAME, getPromptsDir } from "../config";
|
|
5
|
+
import { parseFrontmatter } from "../utils/frontmatter";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Represents a prompt template loaded from a markdown file
|
|
@@ -44,7 +44,7 @@ handlebars.registerHelper(
|
|
|
44
44
|
const suffix = (options.hash.suffix as string) ?? "";
|
|
45
45
|
const rawSeparator = (options.hash.join as string) ?? "\n";
|
|
46
46
|
const separator = rawSeparator.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
47
|
-
return context.map(
|
|
47
|
+
return context.map(item => `${prefix}${options.fn(item)}${suffix}`).join(separator);
|
|
48
48
|
},
|
|
49
49
|
);
|
|
50
50
|
|
|
@@ -126,7 +126,7 @@ handlebars.registerHelper(
|
|
|
126
126
|
const headers = headersStr?.split("|") ?? [];
|
|
127
127
|
const separator = headers.map(() => "---").join(" | ");
|
|
128
128
|
const headerRow = headers.length > 0 ? `| ${headers.join(" | ")} |\n| ${separator} |\n` : "";
|
|
129
|
-
const rows = context.map(
|
|
129
|
+
const rows = context.map(item => `| ${options.fn(item).trim()} |`).join("\n");
|
|
130
130
|
return headerRow + rows;
|
|
131
131
|
},
|
|
132
132
|
);
|
|
@@ -235,7 +235,7 @@ function optimizePromptLayout(input: string): string {
|
|
|
235
235
|
// normalize NBSP -> space
|
|
236
236
|
s = s.replace(/\u00A0/g, " ");
|
|
237
237
|
|
|
238
|
-
const lines = s.split("\n").map(
|
|
238
|
+
const lines = s.split("\n").map(line => {
|
|
239
239
|
// 2) remove trailing whitespace (spaces/tabs) per line
|
|
240
240
|
let l = line.replace(/[ \t]+$/g, "");
|
|
241
241
|
|
|
@@ -382,7 +382,7 @@ async function loadTemplatesFromDir(
|
|
|
382
382
|
entries.sort((a, b) => a.split("/").length - b.split("/").length);
|
|
383
383
|
|
|
384
384
|
for (const entry of entries) {
|
|
385
|
-
const fullPath = join(dir, entry);
|
|
385
|
+
const fullPath = path.join(dir, entry);
|
|
386
386
|
const file = Bun.file(fullPath);
|
|
387
387
|
|
|
388
388
|
try {
|
|
@@ -409,7 +409,7 @@ async function loadTemplatesFromDir(
|
|
|
409
409
|
// Get description from frontmatter or first non-empty line
|
|
410
410
|
let description = String(frontmatter.description || "");
|
|
411
411
|
if (!description) {
|
|
412
|
-
const firstLine = body.split("\n").find(
|
|
412
|
+
const firstLine = body.split("\n").find(line => line.trim());
|
|
413
413
|
if (firstLine) {
|
|
414
414
|
// Truncate if too long
|
|
415
415
|
description = firstLine.slice(0, 60);
|
|
@@ -461,11 +461,11 @@ export async function loadPromptTemplates(options: LoadPromptTemplatesOptions =
|
|
|
461
461
|
|
|
462
462
|
// 1. Load global templates from agentDir/prompts/
|
|
463
463
|
// Note: if agentDir is provided, it should be the agent dir, not the prompts dir
|
|
464
|
-
const globalPromptsDir = options.agentDir ? join(options.agentDir, "prompts") : resolvedAgentDir;
|
|
464
|
+
const globalPromptsDir = options.agentDir ? path.join(options.agentDir, "prompts") : resolvedAgentDir;
|
|
465
465
|
templates.push(...(await loadTemplatesFromDir(globalPromptsDir, "user")));
|
|
466
466
|
|
|
467
467
|
// 2. Load project templates from cwd/{CONFIG_DIR_NAME}/prompts/
|
|
468
|
-
const projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
|
|
468
|
+
const projectPromptsDir = path.resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
|
|
469
469
|
templates.push(...(await loadTemplatesFromDir(projectPromptsDir, "project")));
|
|
470
470
|
|
|
471
471
|
return templates;
|
|
@@ -482,7 +482,7 @@ export function expandPromptTemplate(text: string, templates: PromptTemplate[]):
|
|
|
482
482
|
const templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
483
483
|
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
|
|
484
484
|
|
|
485
|
-
const template = templates.find(
|
|
485
|
+
const template = templates.find(t => t.name === templateName);
|
|
486
486
|
if (template) {
|
|
487
487
|
const args = parseCommandArgs(argsString);
|
|
488
488
|
const argsText = args.join(" ");
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { type Settings as SettingsItem, settingsCapability } from "@oh-my-pi/pi-coding-agent/capability/settings";
|
|
5
|
-
import { getAgentDbPath, getAgentDir } from "@oh-my-pi/pi-coding-agent/config";
|
|
6
|
-
import { withFileLock } from "@oh-my-pi/pi-coding-agent/config/file-lock";
|
|
7
|
-
import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery";
|
|
8
|
-
import type { SymbolPreset } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
9
|
-
import { AgentStorage } from "@oh-my-pi/pi-coding-agent/session/agent-storage";
|
|
10
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
11
4
|
import { YAML } from "bun";
|
|
5
|
+
import { type Settings as SettingsItem, settingsCapability } from "../capability/settings";
|
|
6
|
+
import { getAgentDbPath, getAgentDir } from "../config";
|
|
7
|
+
import { loadCapability } from "../discovery";
|
|
8
|
+
import type { SymbolPreset } from "../modes/theme/theme";
|
|
9
|
+
import { AgentStorage } from "../session/agent-storage";
|
|
10
|
+
import { withFileLock } from "./file-lock";
|
|
12
11
|
|
|
13
12
|
export interface CompactionSettings {
|
|
14
13
|
enabled?: boolean; // default: true
|
|
@@ -203,6 +202,7 @@ export interface Settings {
|
|
|
203
202
|
interruptMode?: "immediate" | "wait";
|
|
204
203
|
theme?: string;
|
|
205
204
|
symbolPreset?: SymbolPreset; // default: uses theme's preset or "unicode"
|
|
205
|
+
colorBlindMode?: boolean; // default: false (use blue instead of green for diff additions)
|
|
206
206
|
compaction?: CompactionSettings;
|
|
207
207
|
branchSummary?: BranchSummarySettings;
|
|
208
208
|
retry?: RetrySettings;
|
|
@@ -343,7 +343,7 @@ function normalizeBashInterceptorSettings(
|
|
|
343
343
|
patterns = DEFAULT_BASH_INTERCEPTOR_RULES;
|
|
344
344
|
} else if (Array.isArray(rawPatterns)) {
|
|
345
345
|
patterns = rawPatterns
|
|
346
|
-
.map(
|
|
346
|
+
.map(rule => normalizeBashInterceptorRule(rule))
|
|
347
347
|
.filter((rule): rule is BashInterceptorRule => rule !== null);
|
|
348
348
|
} else {
|
|
349
349
|
patterns = DEFAULT_BASH_INTERCEPTOR_RULES;
|
|
@@ -372,7 +372,7 @@ function hasNerdFonts(): boolean {
|
|
|
372
372
|
const termProgram = (process.env.TERM_PROGRAM || "").toLowerCase();
|
|
373
373
|
const term = (process.env.TERM || "").toLowerCase();
|
|
374
374
|
const nerdTerms = ["iterm", "wezterm", "kitty", "ghostty", "alacritty"];
|
|
375
|
-
cachedNerdFonts = nerdTerms.some(
|
|
375
|
+
cachedNerdFonts = nerdTerms.some(candidate => termProgram.includes(candidate) || term.includes(candidate));
|
|
376
376
|
return cachedNerdFonts;
|
|
377
377
|
}
|
|
378
378
|
|
|
@@ -503,7 +503,7 @@ export class SettingsManager {
|
|
|
503
503
|
* @returns Configured SettingsManager with merged global and user settings
|
|
504
504
|
*/
|
|
505
505
|
static async create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): Promise<SettingsManager> {
|
|
506
|
-
const configPath = join(agentDir, "config.yml");
|
|
506
|
+
const configPath = path.join(agentDir, "config.yml");
|
|
507
507
|
const storage = await AgentStorage.open(getAgentDbPath(agentDir));
|
|
508
508
|
|
|
509
509
|
// Migrate from legacy storage if config.yml doesn't exist
|
|
@@ -521,7 +521,7 @@ export class SettingsManager {
|
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
// Load persisted settings from config.yml
|
|
524
|
-
const storedSettings = SettingsManager.loadFromYaml(configPath);
|
|
524
|
+
const storedSettings = await SettingsManager.loadFromYaml(configPath);
|
|
525
525
|
globalSettings = deepMergeSettings(globalSettings, storedSettings);
|
|
526
526
|
|
|
527
527
|
// Load project settings before construction (constructor is sync)
|
|
@@ -559,18 +559,19 @@ export class SettingsManager {
|
|
|
559
559
|
* @param configPath - Path to config.yml, or null for in-memory mode
|
|
560
560
|
* @returns Parsed and migrated settings, or empty object if file doesn't exist
|
|
561
561
|
*/
|
|
562
|
-
private static loadFromYaml(configPath: string | null): Settings {
|
|
563
|
-
if (!configPath
|
|
562
|
+
private static async loadFromYaml(configPath: string | null): Promise<Settings> {
|
|
563
|
+
if (!configPath) {
|
|
564
564
|
return {};
|
|
565
565
|
}
|
|
566
566
|
try {
|
|
567
|
-
const content =
|
|
567
|
+
const content = await Bun.file(configPath).text();
|
|
568
568
|
const parsed = YAML.parse(content);
|
|
569
569
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
570
570
|
return {};
|
|
571
571
|
}
|
|
572
572
|
return SettingsManager.migrateSettings(parsed as Record<string, unknown>);
|
|
573
573
|
} catch (error) {
|
|
574
|
+
if (isEnoent(error)) return {};
|
|
574
575
|
logger.warn("SettingsManager failed to load config.yml", { path: configPath, error: String(error) });
|
|
575
576
|
return {};
|
|
576
577
|
}
|
|
@@ -582,31 +583,37 @@ export class SettingsManager {
|
|
|
582
583
|
* Only migrates if config.yml doesn't exist.
|
|
583
584
|
*/
|
|
584
585
|
private static async migrateToYaml(storage: AgentStorage, agentDir: string, configPath: string): Promise<void> {
|
|
585
|
-
|
|
586
|
-
|
|
586
|
+
try {
|
|
587
|
+
await Bun.file(configPath).text();
|
|
588
|
+
return;
|
|
589
|
+
} catch (err) {
|
|
590
|
+
if (!isEnoent(err)) {
|
|
591
|
+
logger.warn("SettingsManager failed to check config.yml", { path: configPath, error: String(err) });
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
587
595
|
|
|
588
596
|
let settings: Settings = {};
|
|
589
597
|
let migrated = false;
|
|
590
598
|
|
|
591
599
|
// 1. Try to migrate from settings.json (oldest legacy format)
|
|
592
|
-
const settingsJsonPath = join(agentDir, "settings.json");
|
|
600
|
+
const settingsJsonPath = path.join(agentDir, "settings.json");
|
|
593
601
|
try {
|
|
594
|
-
const
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
} catch (error) {
|
|
604
|
-
logger.warn("SettingsManager failed to backup settings.json", { error: String(error) });
|
|
605
|
-
}
|
|
602
|
+
const parsed = JSON.parse(await Bun.file(settingsJsonPath).text());
|
|
603
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
604
|
+
settings = deepMergeSettings(settings, SettingsManager.migrateSettings(parsed));
|
|
605
|
+
migrated = true;
|
|
606
|
+
// Backup settings.json
|
|
607
|
+
try {
|
|
608
|
+
await fs.rename(settingsJsonPath, `${settingsJsonPath}.bak`);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
logger.warn("SettingsManager failed to backup settings.json", { error: String(error) });
|
|
606
611
|
}
|
|
607
612
|
}
|
|
608
613
|
} catch (error) {
|
|
609
|
-
|
|
614
|
+
if (!isEnoent(error)) {
|
|
615
|
+
logger.warn("SettingsManager failed to read settings.json", { error: String(error) });
|
|
616
|
+
}
|
|
610
617
|
}
|
|
611
618
|
|
|
612
619
|
// 2. Migrate from agent.db settings table
|
|
@@ -690,7 +697,7 @@ export class SettingsManager {
|
|
|
690
697
|
const configPath = this.configPath;
|
|
691
698
|
try {
|
|
692
699
|
await withFileLock(configPath, async () => {
|
|
693
|
-
const currentSettings = SettingsManager.loadFromYaml(configPath);
|
|
700
|
+
const currentSettings = await SettingsManager.loadFromYaml(configPath);
|
|
694
701
|
const mergedSettings = deepMergeSettings(currentSettings, this.globalSettings);
|
|
695
702
|
this.globalSettings = mergedSettings;
|
|
696
703
|
await Bun.write(configPath, YAML.stringify(this.globalSettings, null, 2));
|
|
@@ -788,6 +795,15 @@ export class SettingsManager {
|
|
|
788
795
|
await this.save();
|
|
789
796
|
}
|
|
790
797
|
|
|
798
|
+
getColorBlindMode(): boolean {
|
|
799
|
+
return this.settings.colorBlindMode ?? false;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async setColorBlindMode(enabled: boolean): Promise<void> {
|
|
803
|
+
this.globalSettings.colorBlindMode = enabled;
|
|
804
|
+
await this.save();
|
|
805
|
+
}
|
|
806
|
+
|
|
791
807
|
getDefaultThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined {
|
|
792
808
|
return this.settings.defaultThinkingLevel;
|
|
793
809
|
}
|