@oh-my-pi/pi-coding-agent 15.12.3 → 15.13.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 +347 -7
- package/dist/cli.js +1615 -1231
- package/dist/types/async/job-manager.d.ts +15 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/cli/args.d.ts +19 -2
- package/dist/types/cli/models-cli.d.ts +49 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/collab/protocol.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +0 -3
- package/dist/types/commands/models.d.ts +33 -0
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/commands/token.d.ts +25 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +3 -3
- package/dist/types/config/model-registry.d.ts +17 -0
- package/dist/types/config/models-config-schema.d.ts +13 -1
- package/dist/types/config/models-config.d.ts +8 -2
- package/dist/types/config/settings-schema.d.ts +281 -58
- package/dist/types/edit/hashline/params.d.ts +1 -1
- package/dist/types/edit/modes/apply-patch.d.ts +1 -1
- package/dist/types/edit/modes/patch.d.ts +1 -1
- package/dist/types/edit/modes/replace.d.ts +1 -1
- package/dist/types/export/html/index.d.ts +2 -1
- package/dist/types/extensibility/custom-commands/types.d.ts +2 -2
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +49 -3
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/hooks/types.d.ts +2 -2
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/goals/tools/goal-tool.d.ts +1 -1
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/local-protocol.d.ts +4 -2
- package/dist/types/lsp/types.d.ts +1 -1
- package/dist/types/main.d.ts +4 -3
- package/dist/types/mcp/manager.d.ts +8 -0
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/memories/index.d.ts +7 -0
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/mnemopi/config.d.ts +28 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -2
- package/dist/types/modes/components/agent-hub.d.ts +6 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
- package/dist/types/modes/components/custom-editor.d.ts +39 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +9 -5
- package/dist/types/modes/components/status-line/types.d.ts +2 -1
- package/dist/types/modes/components/tool-execution.d.ts +26 -16
- package/dist/types/modes/components/transcript-container.d.ts +23 -2
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/event-controller.d.ts +0 -17
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +27 -6
- package/dist/types/modes/magic-keywords.d.ts +13 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/theme/theme.d.ts +13 -2
- package/dist/types/modes/types.d.ts +8 -7
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +17 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +28 -35
- package/dist/types/session/agent-storage.d.ts +2 -1
- package/dist/types/session/indexed-session-storage.d.ts +3 -3
- package/dist/types/session/messages.d.ts +8 -10
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +85 -462
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -7
- package/dist/types/session/snapcompact-inline.d.ts +12 -1
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +4 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/index.d.ts +9 -1
- package/dist/types/task/types.d.ts +37 -1
- package/dist/types/tools/ask.d.ts +1 -1
- package/dist/types/tools/ast-edit.d.ts +1 -1
- package/dist/types/tools/ast-grep.d.ts +1 -1
- package/dist/types/tools/bash.d.ts +3 -3
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
- package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
- package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
- package/dist/types/tools/browser/registry.d.ts +16 -3
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/checkpoint.d.ts +1 -1
- package/dist/types/tools/debug.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +1 -1
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/gh.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +1 -1
- package/dist/types/tools/index.d.ts +14 -2
- package/dist/types/tools/inspect-image.d.ts +1 -1
- package/dist/types/tools/irc.d.ts +2 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/dist/types/tools/memory-recall.d.ts +1 -1
- package/dist/types/tools/memory-reflect.d.ts +1 -1
- package/dist/types/tools/memory-retain.d.ts +1 -1
- package/dist/types/tools/plan-mode-guard.d.ts +10 -0
- package/dist/types/tools/read.d.ts +1 -1
- package/dist/types/tools/render-mermaid.d.ts +1 -1
- package/dist/types/tools/renderers.d.ts +7 -11
- package/dist/types/tools/resolve.d.ts +1 -1
- package/dist/types/tools/review.d.ts +1 -1
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/ssh.d.ts +2 -2
- package/dist/types/tools/todo.d.ts +2 -2
- package/dist/types/tools/tts.d.ts +26 -1
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +4 -3
- package/dist/types/utils/image-loading.d.ts +18 -1
- package/dist/types/utils/thinking-display.d.ts +17 -0
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/utils/tools-manager.test.d.ts +1 -0
- package/dist/types/web/scrapers/github.d.ts +1 -1
- package/dist/types/web/search/index.d.ts +1 -1
- package/package.json +17 -16
- package/src/async/job-manager.ts +49 -0
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/storage.ts +2 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/cli/args.ts +56 -10
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/models-cli.ts +427 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli-commands.ts +3 -0
- package/src/cli.ts +45 -13
- package/src/collab/host.ts +10 -13
- package/src/collab/protocol.ts +1 -1
- package/src/commands/launch.ts +0 -3
- package/src/commands/models.ts +61 -0
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commands/token.ts +89 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -1
- package/src/commit/agentic/tools/git-file-diff.ts +1 -1
- package/src/commit/agentic/tools/git-hunk.ts +1 -1
- package/src/commit/agentic/tools/git-overview.ts +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +1 -1
- package/src/commit/agentic/tools/propose-commit.ts +1 -1
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -1
- package/src/commit/agentic/tools/split-commit.ts +1 -1
- package/src/commit/analysis/summary.ts +1 -1
- package/src/commit/changelog/generate.ts +1 -1
- package/src/commit/shared-llm.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-discovery.ts +11 -5
- package/src/config/model-registry.ts +79 -21
- package/src/config/model-resolver.ts +2 -2
- package/src/config/models-config-schema.ts +5 -2
- package/src/config/models-config.ts +2 -1
- package/src/config/settings-schema.ts +266 -32
- package/src/config/settings.ts +10 -0
- package/src/discovery/builtin.ts +23 -1
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +41 -1
- package/src/edit/hashline/params.ts +1 -1
- package/src/edit/modes/apply-patch.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/modes/replace.ts +1 -1
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/agent-bridge.ts +1 -1
- package/src/eval/completion-bridge.ts +1 -1
- package/src/eval/js/shared/prelude.txt +69 -17
- package/src/export/html/index.ts +3 -6
- package/src/export/html/template.js +24 -2
- package/src/export/html/tool-views.generated.js +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -1
- package/src/extensibility/custom-commands/types.ts +2 -2
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +2 -2
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +54 -3
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
- package/src/extensibility/plugins/loader.ts +30 -19
- package/src/extensibility/plugins/manager.ts +221 -90
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +101 -5
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/docs-index.generated.ts +13 -10
- package/src/internal-urls/history-protocol.ts +1 -1
- package/src/internal-urls/local-protocol.ts +29 -7
- package/src/lsp/types.ts +1 -1
- package/src/main.ts +27 -32
- package/src/mcp/config-writer.ts +7 -3
- package/src/mcp/manager.ts +11 -0
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/memories/index.ts +149 -12
- package/src/memories/storage.ts +2 -1
- package/src/memory-backend/local-backend.ts +11 -5
- package/src/mnemopi/backend.ts +1 -0
- package/src/mnemopi/config.ts +112 -12
- package/src/modes/acp/acp-agent.ts +8 -53
- package/src/modes/acp/acp-event-mapper.ts +5 -1
- package/src/modes/components/agent-hub.ts +51 -5
- package/src/modes/components/assistant-message.ts +12 -44
- package/src/modes/components/compaction-summary-message.ts +125 -26
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +164 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/component.ts +54 -157
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +2 -1
- package/src/modes/components/tool-execution.ts +82 -43
- package/src/modes/components/transcript-container.ts +70 -1
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message.ts +4 -2
- package/src/modes/controllers/command-controller.ts +14 -16
- package/src/modes/controllers/event-controller.ts +101 -73
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +311 -57
- package/src/modes/controllers/mcp-command-controller.ts +44 -3
- package/src/modes/controllers/selector-controller.ts +68 -12
- package/src/modes/controllers/streaming-reveal.ts +4 -3
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +20 -0
- package/src/modes/interactive-mode.ts +288 -48
- package/src/modes/magic-keywords.ts +27 -5
- package/src/modes/rpc/rpc-mode.ts +146 -14
- package/src/modes/rpc/rpc-subagents.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +8 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/theme/theme.ts +99 -51
- package/src/modes/types.ts +6 -7
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +36 -7
- package/src/priority.json +5 -1
- package/src/prompts/agents/task.md +1 -0
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/read-path.md +6 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/empty-stop-retry.md +4 -6
- package/src/prompts/system/subagent-system-prompt.md +4 -0
- package/src/prompts/system/system-prompt.md +10 -5
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/tools/job.md +1 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/registry/agent-registry.ts +30 -0
- package/src/sdk.ts +103 -43
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +331 -318
- package/src/session/agent-storage.ts +18 -9
- package/src/session/history-storage.ts +3 -2
- package/src/session/indexed-session-storage.ts +7 -10
- package/src/session/messages.ts +9 -11
- package/src/session/session-context.ts +352 -0
- package/src/session/session-dump-format.ts +4 -2
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +968 -3064
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -30
- package/src/session/snapcompact-inline.ts +21 -1
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +40 -4
- package/src/slash-commands/helpers/logout.ts +88 -0
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +247 -60
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/agents.ts +1 -2
- package/src/task/executor.ts +49 -15
- package/src/task/index.ts +60 -6
- package/src/task/render.ts +83 -8
- package/src/task/types.ts +54 -1
- package/src/tools/ask.ts +9 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +1 -1
- package/src/tools/bash.ts +5 -4
- package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
- package/src/tools/browser/cmux/rpc.ts +156 -0
- package/src/tools/browser/cmux/socket-client.ts +309 -0
- package/src/tools/browser/registry.ts +37 -3
- package/src/tools/browser/render.ts +6 -1
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +189 -18
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +16 -1
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +4 -3
- package/src/tools/eval.ts +11 -6
- package/src/tools/fetch.ts +13 -2
- package/src/tools/find.ts +1 -1
- package/src/tools/gh.ts +1 -1
- package/src/tools/github-cache.ts +2 -1
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +43 -5
- package/src/tools/inspect-image.ts +3 -1
- package/src/tools/irc.ts +11 -3
- package/src/tools/job.ts +15 -3
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +1 -1
- package/src/tools/memory-retain.ts +1 -1
- package/src/tools/plan-mode-guard.ts +53 -19
- package/src/tools/read.ts +8 -2
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/renderers.ts +7 -11
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/search-tool-bm25.ts +1 -1
- package/src/tools/search.ts +1 -1
- package/src/tools/ssh.ts +5 -4
- package/src/tools/todo.ts +2 -2
- package/src/tools/tts.ts +204 -93
- package/src/tools/write.ts +19 -3
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +497 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/utils/clipboard.ts +35 -18
- package/src/utils/image-loading.ts +35 -4
- package/src/utils/thinking-display.ts +37 -0
- package/src/utils/title-generator.ts +48 -5
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.test.ts +25 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/scrapers/github.ts +96 -0
- package/src/web/search/index.ts +14 -1
- package/src/web/search/providers/searxng.ts +13 -1
- package/dist/types/cli/list-models.d.ts +0 -30
- package/dist/types/stt/setup.d.ts +0 -18
- package/src/cli/list-models.ts +0 -194
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
2
3
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { $env, isEnoent, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
|
+
import { resolveLocalRoot } from "../../internal-urls";
|
|
6
8
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
7
9
|
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
|
8
10
|
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
|
9
11
|
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
10
|
-
import { materializeImageReferenceLinks } from "../../modes/image-references";
|
|
12
|
+
import { materializeImageReferenceLinks, shiftImageMarkers } from "../../modes/image-references";
|
|
11
13
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
12
14
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
13
15
|
import manualContinuePrompt from "../../prompts/system/manual-continue.md" with { type: "text" };
|
|
@@ -42,12 +44,44 @@ function hasPasteText(value: unknown): value is PasteTarget {
|
|
|
42
44
|
return typeof value === "object" && value !== null && typeof (value as PasteTarget).pasteText === "function";
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/** Wrap pasted text in a fenced code block, using a backtick fence longer than any run of
|
|
48
|
+
* backticks already in the content so an embedded fence cannot terminate the block early. */
|
|
49
|
+
function wrapPasteInCodeBlock(content: string): string {
|
|
50
|
+
let longestRun = 0;
|
|
51
|
+
let run = 0;
|
|
52
|
+
for (let i = 0; i < content.length; i++) {
|
|
53
|
+
if (content.charCodeAt(i) === 96 /* backtick */) {
|
|
54
|
+
run++;
|
|
55
|
+
if (run > longestRun) longestRun = run;
|
|
56
|
+
} else {
|
|
57
|
+
run = 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
61
|
+
return `${fence}\n${content}\n${fence}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Wrap pasted text in `<pasted_text>` tags so the model treats it as one quoted block. */
|
|
65
|
+
function wrapPasteInXml(content: string): string {
|
|
66
|
+
return `<pasted_text>\n${content}\n</pasted_text>`;
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
const TINY_TITLE_PROGRESS_DONE_TTL_MS = 3_000;
|
|
46
70
|
// A cached model fires its file-load events in a short burst and then goes silent
|
|
47
71
|
// while onnxruntime builds the session; a genuine download keeps streaming progress
|
|
48
72
|
// events for seconds. Only reveal the bar once a still-incomplete event arrives after
|
|
49
73
|
// this grace window, so an already-downloaded model never flashes the bar.
|
|
50
74
|
const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000;
|
|
75
|
+
// Double-tap ← on an empty editor opens the Agent Hub (and, in a focused
|
|
76
|
+
// subagent view, ←← returns to the main session). The second tap must land
|
|
77
|
+
// inside this window. The lower bound rejects terminal-synthesized arrow-key
|
|
78
|
+
// bursts: "click to move cursor" / pointer features in iTerm2, WezTerm, kitty,
|
|
79
|
+
// and tmux emit several arrow keys in a single stdin read (sub-millisecond
|
|
80
|
+
// apart) on a stray click, which used to pop the hub with no key ever pressed.
|
|
81
|
+
// Three or more rapid taps are likewise treated as a burst, not a gesture. A
|
|
82
|
+
// deliberate human double-tap is always tens of milliseconds apart.
|
|
83
|
+
const LEFT_DOUBLE_TAP_MIN_GAP_MS = 40;
|
|
84
|
+
const LEFT_DOUBLE_TAP_MAX_GAP_MS = 500;
|
|
51
85
|
|
|
52
86
|
export class InputController {
|
|
53
87
|
constructor(
|
|
@@ -61,6 +95,13 @@ export class InputController {
|
|
|
61
95
|
|
|
62
96
|
#enhancedPaste?: EnhancedPasteController;
|
|
63
97
|
#focusedLeftTapListenerInstalled = false;
|
|
98
|
+
// Tap counter for the double-← gesture; reset whenever a quiet gap
|
|
99
|
+
// (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
|
|
100
|
+
// #detectLeftDoubleTap.
|
|
101
|
+
#leftTapCount = 0;
|
|
102
|
+
// Sequential index for `local://attachment-N` references created by the large-paste "attach as
|
|
103
|
+
// file" action. Seeded from 0 and bumped past any existing attachment files in #attachPasteAsFile.
|
|
104
|
+
#attachmentCounter = 0;
|
|
64
105
|
|
|
65
106
|
#showTinyTitleDownloadProgress(modelKey: string): void {
|
|
66
107
|
if (!isTinyTitleLocalModelKey(modelKey)) return;
|
|
@@ -120,10 +161,38 @@ export class InputController {
|
|
|
120
161
|
});
|
|
121
162
|
}
|
|
122
163
|
this.ctx.editor.onEscape = () => {
|
|
164
|
+
// Active context maintenance owns Esc: auto/manual compaction,
|
|
165
|
+
// handoff generation, and auto-retry backoff all advertise
|
|
166
|
+
// "(esc to cancel)". Dispatch on live session state instead of
|
|
167
|
+
// swapping onEscape handlers — interleaved start/end events used
|
|
168
|
+
// to clobber the single saved-handler slot (auto-compaction start
|
|
169
|
+
// → /compact → auto end → manual finally), leaving Esc wired to a
|
|
170
|
+
// stale no-op closure until restart.
|
|
171
|
+
const viewSession = this.ctx.viewSession;
|
|
172
|
+
let aborted = false;
|
|
173
|
+
if (viewSession.isCompacting) {
|
|
174
|
+
try {
|
|
175
|
+
viewSession.abortCompaction();
|
|
176
|
+
} catch {}
|
|
177
|
+
aborted = true;
|
|
178
|
+
}
|
|
179
|
+
if (viewSession.isGeneratingHandoff) {
|
|
180
|
+
try {
|
|
181
|
+
viewSession.abortHandoff();
|
|
182
|
+
} catch {}
|
|
183
|
+
aborted = true;
|
|
184
|
+
}
|
|
185
|
+
if (viewSession.isRetrying) {
|
|
186
|
+
try {
|
|
187
|
+
viewSession.abortRetry();
|
|
188
|
+
} catch {}
|
|
189
|
+
aborted = true;
|
|
190
|
+
}
|
|
191
|
+
if (aborted) return;
|
|
192
|
+
|
|
123
193
|
if (this.ctx.loopModeEnabled) {
|
|
124
194
|
this.ctx.pauseLoop();
|
|
125
195
|
if (this.ctx.session.isStreaming) {
|
|
126
|
-
this.ctx.notifyInterrupting();
|
|
127
196
|
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
128
197
|
} else {
|
|
129
198
|
this.ctx.cancelPendingSubmission();
|
|
@@ -153,7 +222,6 @@ export class InputController {
|
|
|
153
222
|
// session is never streaming, so the native abort path below would
|
|
154
223
|
// no-op.
|
|
155
224
|
if (this.ctx.collabGuest.state?.isStreaming || this.ctx.loadingAnimation) {
|
|
156
|
-
if (!this.ctx.collabGuest.readOnly) this.ctx.notifyInterrupting();
|
|
157
225
|
this.ctx.collabGuest.sendAbort();
|
|
158
226
|
}
|
|
159
227
|
return;
|
|
@@ -176,7 +244,6 @@ export class InputController {
|
|
|
176
244
|
this.ctx.isPythonMode = false;
|
|
177
245
|
this.ctx.updateEditorBorderColor();
|
|
178
246
|
} else if (this.ctx.session.isStreaming) {
|
|
179
|
-
this.ctx.notifyInterrupting();
|
|
180
247
|
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
181
248
|
} else if (this.ctx.editor.getText().trim()) {
|
|
182
249
|
// Esc with typed text clears the draft instead of (or before) any double-Esc action
|
|
@@ -244,6 +311,7 @@ export class InputController {
|
|
|
244
311
|
this.ctx.keybindings.getKeys("app.clipboard.pasteTextRaw"),
|
|
245
312
|
);
|
|
246
313
|
this.ctx.editor.onPasteTextRaw = () => void this.handleClipboardTextRawPaste();
|
|
314
|
+
this.ctx.editor.onLargePaste = (text, lineCount) => this.handleLargePaste(text, lineCount);
|
|
247
315
|
this.ctx.editor.setActionKeys(
|
|
248
316
|
"app.clipboard.copyPrompt",
|
|
249
317
|
this.ctx.keybindings.getKeys("app.clipboard.copyPrompt"),
|
|
@@ -279,6 +347,12 @@ export class InputController {
|
|
|
279
347
|
for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
|
|
280
348
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
|
|
281
349
|
}
|
|
350
|
+
// Hold the space bar to push-to-talk: the editor recognizes the auto-repeat burst, tracks
|
|
351
|
+
// the spam back out, and toggles STT on hold start / release. Gated on `stt.enabled` so a
|
|
352
|
+
// disabled STT leaves the space bar typing normally.
|
|
353
|
+
this.ctx.editor.sttHoldEnabled = () => settings.get("stt.enabled");
|
|
354
|
+
this.ctx.editor.onSpaceHoldStart = () => void this.ctx.handleSTTToggle();
|
|
355
|
+
this.ctx.editor.onSpaceHoldEnd = () => void this.ctx.handleSTTToggle();
|
|
282
356
|
for (const key of this.ctx.keybindings.getKeys("app.clipboard.copyLine")) {
|
|
283
357
|
this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyCurrentLine());
|
|
284
358
|
}
|
|
@@ -292,18 +366,16 @@ export class InputController {
|
|
|
292
366
|
|
|
293
367
|
// Double-tap left arrow on an empty editor: opens the agent hub from the
|
|
294
368
|
// main session, or returns the focused subagent view to the main session.
|
|
295
|
-
// Focused ←← intentionally matches Esc.
|
|
369
|
+
// Focused ←← intentionally matches Esc. From the main session the gesture
|
|
370
|
+
// stays inert when there are no subagents (requireContent); the explicit
|
|
371
|
+
// hub key still opens the empty roster.
|
|
296
372
|
this.ctx.editor.onLeftAtStart = () => {
|
|
297
373
|
if (this.ctx.focusedAgentId) {
|
|
298
374
|
this.#handleFocusedLeftTap();
|
|
299
375
|
return;
|
|
300
376
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
this.ctx.lastLeftTapTime = 0;
|
|
304
|
-
this.ctx.showAgentHub();
|
|
305
|
-
} else {
|
|
306
|
-
this.ctx.lastLeftTapTime = now;
|
|
377
|
+
if (this.#detectLeftDoubleTap()) {
|
|
378
|
+
this.ctx.showAgentHub({ requireContent: true });
|
|
307
379
|
}
|
|
308
380
|
};
|
|
309
381
|
|
|
@@ -322,13 +394,37 @@ export class InputController {
|
|
|
322
394
|
}
|
|
323
395
|
|
|
324
396
|
#handleFocusedLeftTap(): void {
|
|
397
|
+
if (this.#detectLeftDoubleTap()) {
|
|
398
|
+
void this.ctx.unfocusSession();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Detect a deliberate double-← gesture, rejecting terminal-synthesized arrow
|
|
404
|
+
* bursts. Returns true only on the *second* tap of a fresh sequence when it
|
|
405
|
+
* lands a human-plausible interval after the first
|
|
406
|
+
* (`[LEFT_DOUBLE_TAP_MIN_GAP_MS, LEFT_DOUBLE_TAP_MAX_GAP_MS)`). Taps closer
|
|
407
|
+
* than the lower bound, or any third-and-later tap before a quiet gap, are a
|
|
408
|
+
* burst and never fire — so a stray click that makes the terminal emit a run
|
|
409
|
+
* of ← keys can no longer pop the Agent Hub.
|
|
410
|
+
*/
|
|
411
|
+
#detectLeftDoubleTap(): boolean {
|
|
325
412
|
const now = Date.now();
|
|
326
|
-
|
|
413
|
+
const sinceLast = now - this.ctx.lastLeftTapTime;
|
|
414
|
+
this.ctx.lastLeftTapTime = now;
|
|
415
|
+
if (sinceLast >= LEFT_DOUBLE_TAP_MAX_GAP_MS) {
|
|
416
|
+
// Quiet gap: this tap starts a fresh sequence.
|
|
417
|
+
this.#leftTapCount = 1;
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
this.#leftTapCount += 1;
|
|
421
|
+
if (this.#leftTapCount === 2 && sinceLast >= LEFT_DOUBLE_TAP_MIN_GAP_MS) {
|
|
422
|
+
// Exactly two taps, the second a human-plausible interval after the first.
|
|
423
|
+
this.#leftTapCount = 0;
|
|
327
424
|
this.ctx.lastLeftTapTime = 0;
|
|
328
|
-
|
|
329
|
-
} else {
|
|
330
|
-
this.ctx.lastLeftTapTime = now;
|
|
425
|
+
return true;
|
|
331
426
|
}
|
|
427
|
+
return false;
|
|
332
428
|
}
|
|
333
429
|
|
|
334
430
|
#setupEnhancedPaste(): void {
|
|
@@ -376,22 +472,16 @@ export class InputController {
|
|
|
376
472
|
return;
|
|
377
473
|
}
|
|
378
474
|
|
|
379
|
-
// Empty submit while streaming with queued
|
|
380
|
-
//
|
|
381
|
-
// waiting for the current tool/model boundary.
|
|
475
|
+
// Empty submit while streaming with queued messages: abort the active
|
|
476
|
+
// turn and let the post-unwind drain deliver the agent-core queue.
|
|
382
477
|
if (!text && this.ctx.session.isStreaming) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
await
|
|
478
|
+
if (this.ctx.session.queuedMessageCount > 0) {
|
|
479
|
+
const aborting = this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
480
|
+
await aborting;
|
|
386
481
|
this.ctx.updatePendingMessagesDisplay();
|
|
387
482
|
this.ctx.ui.requestRender();
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (this.ctx.session.queuedMessageCount > 0) {
|
|
391
|
-
// Preserve the existing empty-submit flush for non-steer queues.
|
|
392
|
-
await this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
393
|
-
return;
|
|
394
483
|
}
|
|
484
|
+
return;
|
|
395
485
|
}
|
|
396
486
|
|
|
397
487
|
if (!text) return;
|
|
@@ -663,9 +753,9 @@ export class InputController {
|
|
|
663
753
|
async #submitToFocusedSession(text: string, streamingBehavior: "steer" | "followUp"): Promise<void> {
|
|
664
754
|
const target = this.ctx.viewSession;
|
|
665
755
|
if (!text) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
await
|
|
756
|
+
if (target.isStreaming && target.queuedMessageCount > 0) {
|
|
757
|
+
const aborting = target.abort({ reason: USER_INTERRUPT_LABEL });
|
|
758
|
+
await aborting;
|
|
669
759
|
this.ctx.updatePendingMessagesDisplay();
|
|
670
760
|
this.ctx.ui.requestRender();
|
|
671
761
|
}
|
|
@@ -702,6 +792,18 @@ export class InputController {
|
|
|
702
792
|
this.ctx.clearEditor();
|
|
703
793
|
this.ctx.lastSigintTime = now;
|
|
704
794
|
}
|
|
795
|
+
// Sync-flush the session JSONL so in-flight writes survive a hard exit.
|
|
796
|
+
// The TUI consumes Ctrl+C as a key event in raw mode, so postmortem's
|
|
797
|
+
// process-level SIGINT handler never fires. The second press still
|
|
798
|
+
// funnels through shutdown() which awaits its own async flush — the
|
|
799
|
+
// sync flush here is a superset that also covers the first-press case.
|
|
800
|
+
try {
|
|
801
|
+
this.ctx.sessionManager.flushSync();
|
|
802
|
+
} catch (err) {
|
|
803
|
+
logger.warn("session-manager sync flush on Ctrl+C failed", {
|
|
804
|
+
error: err instanceof Error ? err.message : String(err),
|
|
805
|
+
});
|
|
806
|
+
}
|
|
705
807
|
}
|
|
706
808
|
|
|
707
809
|
handleCtrlD(): void {
|
|
@@ -798,15 +900,6 @@ export class InputController {
|
|
|
798
900
|
args: args || undefined,
|
|
799
901
|
lineCount: body ? body.split("\n").length : 0,
|
|
800
902
|
};
|
|
801
|
-
// When the agent is streaming, register the compact slash-form text as
|
|
802
|
-
// the pending-display twin BEFORE dispatching the CustomMessage. The
|
|
803
|
-
// returned tag is embedded in details so AgentSession.#handleAgentEvent
|
|
804
|
-
// can remove the matching display entry when the agent consumes this
|
|
805
|
-
// message (mirrors the user-message dequeue path).
|
|
806
|
-
if (this.ctx.session.isStreaming) {
|
|
807
|
-
const tag = this.ctx.session.enqueueCustomMessageDisplay(text, streamingBehavior);
|
|
808
|
-
details.__pendingDisplayTag = tag;
|
|
809
|
-
}
|
|
810
903
|
await this.ctx.session.promptCustomMessage(
|
|
811
904
|
{
|
|
812
905
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
@@ -815,7 +908,7 @@ export class InputController {
|
|
|
815
908
|
details,
|
|
816
909
|
attribution: "user",
|
|
817
910
|
},
|
|
818
|
-
{ streamingBehavior },
|
|
911
|
+
{ streamingBehavior, queueChipText: text },
|
|
819
912
|
);
|
|
820
913
|
if (this.ctx.session.isStreaming) {
|
|
821
914
|
this.ctx.updatePendingMessagesDisplay();
|
|
@@ -901,22 +994,55 @@ export class InputController {
|
|
|
901
994
|
restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {
|
|
902
995
|
this.ctx.locallySubmittedUserSignatures.clear();
|
|
903
996
|
const { steering, followUp } = this.ctx.session.clearQueue();
|
|
904
|
-
|
|
997
|
+
// Messages typed while compacting live in `compactionQueuedMessages`, not the
|
|
998
|
+
// agent queue `clearQueue()` drains — but the pending bar shows the same
|
|
999
|
+
// "Alt+Up to edit" hint for them (ui-helpers `updatePendingMessagesDisplay`).
|
|
1000
|
+
// Drain them here too so the dequeue restores every message the hint
|
|
1001
|
+
// advertises; otherwise a skill/text queued during compaction is stranded and
|
|
1002
|
+
// Alt+Up reports "No queued messages to restore".
|
|
1003
|
+
const compactionQueued = this.ctx.compactionQueuedMessages;
|
|
1004
|
+
this.ctx.compactionQueuedMessages = [];
|
|
1005
|
+
const allQueued = [
|
|
1006
|
+
...steering,
|
|
1007
|
+
...compactionQueued.filter(e => e.mode === "steer").map(e => ({ text: e.text, images: e.images })),
|
|
1008
|
+
...followUp,
|
|
1009
|
+
...compactionQueued.filter(e => e.mode === "followUp").map(e => ({ text: e.text, images: e.images })),
|
|
1010
|
+
];
|
|
905
1011
|
if (allQueued.length === 0) {
|
|
906
1012
|
this.ctx.updatePendingMessagesDisplay();
|
|
907
1013
|
if (options?.abort) {
|
|
908
|
-
this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
1014
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
909
1015
|
}
|
|
910
1016
|
return 0;
|
|
911
1017
|
}
|
|
912
|
-
|
|
1018
|
+
// Image markers are positional: `[Image #N]` ↔ `pendingImages[N-1]`. Each
|
|
1019
|
+
// queued message numbered its markers against its own local image list
|
|
1020
|
+
// (1..K). Because we prepend the queued text but append the queued images
|
|
1021
|
+
// to `pendingImages`, any existing draft images (M of them) — plus images
|
|
1022
|
+
// already pulled in by earlier queued messages — shift the slot index that
|
|
1023
|
+
// every marker must point to. Bumping each message's markers by the
|
|
1024
|
+
// running offset keeps the merged text aligned with the merged
|
|
1025
|
+
// `pendingImages` order; draft markers stay valid because draft images
|
|
1026
|
+
// keep their original positions.
|
|
1027
|
+
const queuedImages = allQueued.flatMap(e => e.images ?? []);
|
|
1028
|
+
let queuedText: string;
|
|
1029
|
+
if (queuedImages.length > 0) {
|
|
1030
|
+
const parts: string[] = [];
|
|
1031
|
+
let imageOffset = this.ctx.pendingImages.length;
|
|
1032
|
+
for (const entry of allQueued) {
|
|
1033
|
+
parts.push(shiftImageMarkers(entry.text, imageOffset));
|
|
1034
|
+
if (entry.images && entry.images.length > 0) imageOffset += entry.images.length;
|
|
1035
|
+
}
|
|
1036
|
+
queuedText = parts.join("\n\n");
|
|
1037
|
+
} else {
|
|
1038
|
+
queuedText = allQueued.map(e => e.text).join("\n\n");
|
|
1039
|
+
}
|
|
913
1040
|
const currentText = options?.currentText ?? this.ctx.editor.getText();
|
|
914
1041
|
const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
|
|
915
1042
|
this.ctx.editor.setText(combinedText);
|
|
916
1043
|
// Hand queued images back to the pending-image buffer (links are
|
|
917
1044
|
// re-materialized lazily; the restored text already carries the
|
|
918
|
-
// `[Image #N, WxH]` markers).
|
|
919
|
-
const queuedImages = allQueued.flatMap(e => e.images ?? []);
|
|
1045
|
+
// renumbered `[Image #N, WxH]` markers).
|
|
920
1046
|
if (queuedImages.length > 0) {
|
|
921
1047
|
this.ctx.pendingImages.push(...queuedImages);
|
|
922
1048
|
this.ctx.pendingImageLinks.push(...queuedImages.map(() => undefined));
|
|
@@ -924,7 +1050,7 @@ export class InputController {
|
|
|
924
1050
|
}
|
|
925
1051
|
this.ctx.updatePendingMessagesDisplay();
|
|
926
1052
|
if (options?.abort) {
|
|
927
|
-
this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
1053
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
928
1054
|
}
|
|
929
1055
|
return allQueued.length;
|
|
930
1056
|
}
|
|
@@ -990,6 +1116,35 @@ export class InputController {
|
|
|
990
1116
|
return true;
|
|
991
1117
|
}
|
|
992
1118
|
|
|
1119
|
+
/**
|
|
1120
|
+
* Win+Shift+S on Windows 11 leaves the screenshot bitmap on the clipboard
|
|
1121
|
+
* while the terminal pastes a transient packaged-app TempState path
|
|
1122
|
+
* (…\MicrosoftWindows.Client.Core_*\TempState\…) that is already gone — or
|
|
1123
|
+
* never materialized — by the time we read it. Whenever a pasted image path
|
|
1124
|
+
* can't be turned into an image locally, those clipboard bytes are the real
|
|
1125
|
+
* payload, so prefer them before degrading to a text paste.
|
|
1126
|
+
*
|
|
1127
|
+
* Skipped over SSH: the clipboard read would hit the remote host, not the
|
|
1128
|
+
* terminal that holds the screenshot. Returns true when the clipboard owned
|
|
1129
|
+
* the outcome (image attached, or an unsupported-format status surfaced), so
|
|
1130
|
+
* the caller stops without emitting its own degraded diagnostic.
|
|
1131
|
+
*/
|
|
1132
|
+
async #tryPasteClipboardImage(): Promise<boolean> {
|
|
1133
|
+
const env = process.env;
|
|
1134
|
+
if (env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT) return false;
|
|
1135
|
+
try {
|
|
1136
|
+
const image = await this.clipboard.readImage();
|
|
1137
|
+
if (!image) return false;
|
|
1138
|
+
await this.#normalizeAndInsertPastedImage(
|
|
1139
|
+
{ type: "image", data: image.data.toBase64(), mimeType: image.mimeType },
|
|
1140
|
+
`Unsupported clipboard image format: ${image.mimeType}`,
|
|
1141
|
+
);
|
|
1142
|
+
return true;
|
|
1143
|
+
} catch {
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
993
1148
|
async handleImagePathPaste(path: string): Promise<void> {
|
|
994
1149
|
try {
|
|
995
1150
|
const image = await loadImageInput({
|
|
@@ -998,6 +1153,9 @@ export class InputController {
|
|
|
998
1153
|
autoResize: false,
|
|
999
1154
|
});
|
|
1000
1155
|
if (!image) {
|
|
1156
|
+
// Path resolved but is not a readable image (e.g. a zero-byte or
|
|
1157
|
+
// locked transient screenshot file). Prefer the clipboard bytes.
|
|
1158
|
+
if (await this.#tryPasteClipboardImage()) return;
|
|
1001
1159
|
this.ctx.editor.pasteText(path);
|
|
1002
1160
|
this.ctx.ui.requestRender();
|
|
1003
1161
|
this.ctx.showStatus("Pasted path is not a supported image");
|
|
@@ -1016,13 +1174,17 @@ export class InputController {
|
|
|
1016
1174
|
}
|
|
1017
1175
|
if (isEnoent(error)) {
|
|
1018
1176
|
// #2375: the bracketed paste forwarded by a local terminal carries a
|
|
1019
|
-
// path on the *local* filesystem.
|
|
1020
|
-
//
|
|
1021
|
-
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1177
|
+
// path on the *local* filesystem. The bytes may still be on the
|
|
1178
|
+
// clipboard (Win+Shift+S), so try those before giving up.
|
|
1179
|
+
if (await this.#tryPasteClipboardImage()) return;
|
|
1180
|
+
// Over SSH the clipboard lives on the remote host, so the path is
|
|
1181
|
+
// genuinely unreachable; pasting it as text would look like the
|
|
1182
|
+
// image was attached when nothing was sent. Surface an SSH-aware
|
|
1183
|
+
// diagnostic instead. The pasted path is untrusted terminal input —
|
|
1184
|
+
// strip control/ANSI/newlines, collapse home to `~`, and bound the
|
|
1185
|
+
// displayed length before splicing it into the status string.
|
|
1186
|
+
const env = process.env;
|
|
1187
|
+
const overSsh = Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
|
|
1026
1188
|
const displayPath = truncateToWidth(
|
|
1027
1189
|
shortenPath(
|
|
1028
1190
|
sanitizeText(path)
|
|
@@ -1031,8 +1193,6 @@ export class InputController {
|
|
|
1031
1193
|
),
|
|
1032
1194
|
TRUNCATE_LENGTHS.CONTENT,
|
|
1033
1195
|
);
|
|
1034
|
-
const env = process.env;
|
|
1035
|
-
const overSsh = Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
|
|
1036
1196
|
this.ctx.showStatus(
|
|
1037
1197
|
overSsh
|
|
1038
1198
|
? `Image not found at ${displayPath}. Over SSH this path is local to your terminal — paste the image directly (clipboard image-paste shortcut) to send its bytes.`
|
|
@@ -1040,6 +1200,7 @@ export class InputController {
|
|
|
1040
1200
|
);
|
|
1041
1201
|
return;
|
|
1042
1202
|
}
|
|
1203
|
+
if (await this.#tryPasteClipboardImage()) return;
|
|
1043
1204
|
this.ctx.editor.pasteText(path);
|
|
1044
1205
|
this.ctx.ui.requestRender();
|
|
1045
1206
|
this.ctx.showStatus("Failed to read pasted image path");
|
|
@@ -1096,6 +1257,97 @@ export class InputController {
|
|
|
1096
1257
|
}
|
|
1097
1258
|
}
|
|
1098
1259
|
|
|
1260
|
+
/**
|
|
1261
|
+
* Editor `onLargePaste` hook: gate a marker-sized paste behind the large-paste menu. Returns
|
|
1262
|
+
* `true` to intercept (the editor skips its default `[Paste]` marker) once the paste reaches the
|
|
1263
|
+
* configured `paste.largeMenuThreshold` line count; otherwise `false` for default collapse-to-marker
|
|
1264
|
+
* behavior. The async menu is fired and forgotten — the editor only needs the synchronous verdict.
|
|
1265
|
+
*/
|
|
1266
|
+
handleLargePaste(text: string, lineCount: number): boolean {
|
|
1267
|
+
const threshold = this.ctx.settings.get("paste.largeMenuThreshold");
|
|
1268
|
+
if (!(threshold > 0) || lineCount < threshold) return false;
|
|
1269
|
+
void this.presentLargePasteMenu(text, lineCount);
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* Present the large-paste menu and apply the chosen action: wrap in a code block or in XML tags
|
|
1275
|
+
* (both collapse to a `[Paste]` marker that expands on submit), or save the text to a file and
|
|
1276
|
+
* reference its path so the agent can `read` it on demand. Cancelling (Esc) falls back to the
|
|
1277
|
+
* default inline paste marker, so the pasted content is never lost.
|
|
1278
|
+
*/
|
|
1279
|
+
async presentLargePasteMenu(text: string, lineCount: number): Promise<void> {
|
|
1280
|
+
const CODE_BLOCK = "Wrap in a code block";
|
|
1281
|
+
const XML = "Wrap in XML tags";
|
|
1282
|
+
const FILE = "Attach as a file";
|
|
1283
|
+
|
|
1284
|
+
let choice: string | undefined;
|
|
1285
|
+
try {
|
|
1286
|
+
choice = await this.ctx.showHookSelector(
|
|
1287
|
+
`Pasted ${lineCount} lines`,
|
|
1288
|
+
[
|
|
1289
|
+
{ label: CODE_BLOCK, description: "Fence the text in a ``` block, collapsed to a marker" },
|
|
1290
|
+
{ label: XML, description: "Wrap the text in <pasted_text> tags, collapsed to a marker" },
|
|
1291
|
+
{ label: FILE, description: "Save the text to a file and reference its path" },
|
|
1292
|
+
],
|
|
1293
|
+
{ helpText: "Esc to paste inline" },
|
|
1294
|
+
);
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
logger.warn("large-paste menu failed", { error: error instanceof Error ? error.message : String(error) });
|
|
1297
|
+
choice = undefined;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
switch (choice) {
|
|
1301
|
+
case CODE_BLOCK:
|
|
1302
|
+
this.ctx.editor.insertPaste(wrapPasteInCodeBlock(text));
|
|
1303
|
+
break;
|
|
1304
|
+
case XML:
|
|
1305
|
+
this.ctx.editor.insertPaste(wrapPasteInXml(text));
|
|
1306
|
+
break;
|
|
1307
|
+
case FILE:
|
|
1308
|
+
await this.#attachPasteAsFile(text, lineCount);
|
|
1309
|
+
break;
|
|
1310
|
+
default:
|
|
1311
|
+
// Esc / cancel: keep the original behavior — collapse to an inline paste marker.
|
|
1312
|
+
this.ctx.editor.insertPaste(text);
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
this.ctx.ui.requestRender();
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Save a large paste to the session's `local://` store and insert a clean `local://attachment-N`
|
|
1320
|
+
* reference into the editor so the agent can `read` it on demand — instead of inlining the text or
|
|
1321
|
+
* leaking a raw temp path. Falls back to an inline paste marker when the write fails, so the
|
|
1322
|
+
* content is never lost.
|
|
1323
|
+
*/
|
|
1324
|
+
async #attachPasteAsFile(text: string, lineCount: number): Promise<void> {
|
|
1325
|
+
try {
|
|
1326
|
+
// Mirror the exact mapping the read tool's local:// resolver uses so a later
|
|
1327
|
+
// `read local://attachment-N` lands on the file written here.
|
|
1328
|
+
const localRoot = resolveLocalRoot({
|
|
1329
|
+
getArtifactsDir: () => this.ctx.sessionManager.getArtifactsDir(),
|
|
1330
|
+
getSessionId: () => this.ctx.sessionManager.getSessionId(),
|
|
1331
|
+
});
|
|
1332
|
+
let name: string;
|
|
1333
|
+
let filePath: string;
|
|
1334
|
+
do {
|
|
1335
|
+
this.#attachmentCounter++;
|
|
1336
|
+
name = `attachment-${this.#attachmentCounter}`;
|
|
1337
|
+
filePath = path.join(localRoot, name);
|
|
1338
|
+
} while (await Bun.file(filePath).exists());
|
|
1339
|
+
await Bun.write(filePath, text);
|
|
1340
|
+
this.ctx.editor.insertText(`local://${name} `);
|
|
1341
|
+
this.ctx.showStatus(`Saved ${lineCount} pasted lines to local://${name}`);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
logger.warn("failed to save large paste to file", {
|
|
1344
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1345
|
+
});
|
|
1346
|
+
this.ctx.editor.insertPaste(text);
|
|
1347
|
+
this.ctx.showError("Failed to save paste to a file — pasted inline instead");
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1099
1351
|
createAutocompleteProvider(commands: SlashCommand[], basePath: string): AutocompleteProvider {
|
|
1100
1352
|
return createPromptActionAutocompleteProvider({
|
|
1101
1353
|
commands,
|
|
@@ -1177,12 +1429,14 @@ export class InputController {
|
|
|
1177
1429
|
this.ctx.updateEditorBorderColor();
|
|
1178
1430
|
// The status line already reports the resolved model + thinking level, so
|
|
1179
1431
|
// the cycle status is just a status-line-style chip track (active role
|
|
1180
|
-
// filled), matching the plan-approval model slider.
|
|
1432
|
+
// filled), matching the plan-approval model slider. It renders into its
|
|
1433
|
+
// own anchored container above the editor (cleared+rebuilt each cycle),
|
|
1434
|
+
// so it updates in place instead of stacking duplicates in the scrollback.
|
|
1181
1435
|
const track = renderSegmentTrack(
|
|
1182
1436
|
cycleOrder.map(role => ({ label: role })),
|
|
1183
1437
|
cycleOrder.indexOf(result.role),
|
|
1184
1438
|
);
|
|
1185
|
-
this.ctx.
|
|
1439
|
+
this.ctx.showModelCycleTrack(track);
|
|
1186
1440
|
} catch (error) {
|
|
1187
1441
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
1188
1442
|
}
|
|
@@ -813,6 +813,43 @@ export class MCPCommandController {
|
|
|
813
813
|
return null;
|
|
814
814
|
}
|
|
815
815
|
|
|
816
|
+
/**
|
|
817
|
+
* Resolve a server for an auth/test operation.
|
|
818
|
+
*
|
|
819
|
+
* Unlike {@link #findConfiguredServer} (which only reads writable OMP config
|
|
820
|
+
* files), this also recognizes runtime-discovered servers that `/mcp list`
|
|
821
|
+
* surfaces but that live in no writable config — e.g. servers from a Claude
|
|
822
|
+
* Code marketplace plugin (`cloudflare:cloudflare-api`), `.cursor/mcp.json`,
|
|
823
|
+
* etc. Without this, `/mcp reauth|test|unauth` reports "not found" for a
|
|
824
|
+
* server the list just showed.
|
|
825
|
+
*
|
|
826
|
+
* For a discovered server, any persisted change is written into the *user*
|
|
827
|
+
* config under the same (namespaced) name; the native provider (priority 100)
|
|
828
|
+
* shadows the discovered entry on the next reload, so an OAuth `auth` block
|
|
829
|
+
* persisted by `/mcp reauth` takes effect. `discovered` lets callers tailor
|
|
830
|
+
* messaging and skip pointless writes when there is nothing to persist.
|
|
831
|
+
*/
|
|
832
|
+
async #resolveServerForAuth(name: string): Promise<{
|
|
833
|
+
filePath: string;
|
|
834
|
+
scope: "user" | "project";
|
|
835
|
+
config: MCPServerConfig;
|
|
836
|
+
discovered: boolean;
|
|
837
|
+
} | null> {
|
|
838
|
+
const found = await this.#findConfiguredServer(name);
|
|
839
|
+
if (found) return { ...found, discovered: false };
|
|
840
|
+
|
|
841
|
+
const config = this.ctx.mcpManager?.getServerConfig(name);
|
|
842
|
+
const source = this.ctx.mcpManager?.getSource(name);
|
|
843
|
+
if (!config || !source) return null;
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
filePath: getMCPConfigPath("user", getProjectDir()),
|
|
847
|
+
scope: "user",
|
|
848
|
+
config,
|
|
849
|
+
discovered: true,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
816
853
|
async #removeManagedOAuthCredential(credentialId: string | undefined): Promise<void> {
|
|
817
854
|
if (!credentialId?.startsWith("mcp_oauth_")) return;
|
|
818
855
|
await this.ctx.session.modelRegistry.authStorage.remove(credentialId);
|
|
@@ -1199,7 +1236,7 @@ export class MCPCommandController {
|
|
|
1199
1236
|
|
|
1200
1237
|
let connection: MCPServerConnection | undefined;
|
|
1201
1238
|
try {
|
|
1202
|
-
const found = await this.#
|
|
1239
|
+
const found = await this.#resolveServerForAuth(name);
|
|
1203
1240
|
|
|
1204
1241
|
if (!found) {
|
|
1205
1242
|
this.ctx.showError(
|
|
@@ -1389,13 +1426,17 @@ export class MCPCommandController {
|
|
|
1389
1426
|
}
|
|
1390
1427
|
|
|
1391
1428
|
try {
|
|
1392
|
-
const found = await this.#
|
|
1429
|
+
const found = await this.#resolveServerForAuth(name);
|
|
1393
1430
|
if (!found) {
|
|
1394
1431
|
this.ctx.showError(`Server "${name}" not found.`);
|
|
1395
1432
|
return;
|
|
1396
1433
|
}
|
|
1397
1434
|
|
|
1398
1435
|
const currentAuth = (found.config as MCPServerConfig & { auth?: MCPAuthConfig }).auth;
|
|
1436
|
+
if (found.discovered && currentAuth?.type !== "oauth") {
|
|
1437
|
+
this.#showMessage(["", theme.fg("muted", `No stored OAuth auth to remove for "${name}".`), ""].join("\n"));
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1399
1440
|
if (currentAuth?.type === "oauth") {
|
|
1400
1441
|
await this.#removeManagedOAuthCredential(currentAuth.credentialId);
|
|
1401
1442
|
}
|
|
@@ -1419,7 +1460,7 @@ export class MCPCommandController {
|
|
|
1419
1460
|
}
|
|
1420
1461
|
|
|
1421
1462
|
try {
|
|
1422
|
-
const found = await this.#
|
|
1463
|
+
const found = await this.#resolveServerForAuth(name);
|
|
1423
1464
|
if (!found) {
|
|
1424
1465
|
this.ctx.showError(`Server "${name}" not found.`);
|
|
1425
1466
|
return;
|