@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,22 +1,18 @@
|
|
|
1
1
|
import { Box, type Component, Markdown } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
3
|
-
import type { CompactionSummaryMessage } from "../../session/messages";
|
|
3
|
+
import type { CompactionSummaryMessage, CustomMessage } from "../../session/messages";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* full history); only the LLM context was reset. Expanding (ctrl+o) reveals
|
|
12
|
-
* the compaction summary below the divider.
|
|
13
|
-
*/
|
|
14
|
-
export class CompactionSummaryMessageComponent implements Component {
|
|
5
|
+
interface SummaryDividerOptions {
|
|
6
|
+
label: () => string;
|
|
7
|
+
detailMarkdown: () => string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class SummaryDividerComponent implements Component {
|
|
15
11
|
#expanded = false;
|
|
16
12
|
#cache?: { width: number; lines: string[] };
|
|
17
13
|
#detail?: Box;
|
|
18
14
|
|
|
19
|
-
constructor(private readonly
|
|
15
|
+
constructor(private readonly options: SummaryDividerOptions) {}
|
|
20
16
|
|
|
21
17
|
setExpanded(expanded: boolean): void {
|
|
22
18
|
if (this.#expanded === expanded) return;
|
|
@@ -44,7 +40,7 @@ export class CompactionSummaryMessageComponent implements Component {
|
|
|
44
40
|
|
|
45
41
|
#divider(width: number): string {
|
|
46
42
|
const rule = theme.tree.horizontal;
|
|
47
|
-
const label =
|
|
43
|
+
const label = this.options.label();
|
|
48
44
|
// sep.dot ships pre-padded (" · "); trim so the hint joins with single spaces.
|
|
49
45
|
const hint = `${theme.sep.dot.trim()} ctrl+o`;
|
|
50
46
|
const plainWidth = Bun.stringWidth(`${label} ${hint}`, { countAnsiEscapeCodes: false });
|
|
@@ -66,22 +62,125 @@ export class CompactionSummaryMessageComponent implements Component {
|
|
|
66
62
|
#detailBox(): Box {
|
|
67
63
|
if (this.#detail) return this.#detail;
|
|
68
64
|
const box = new Box(1, 1, t => theme.bg("customMessageBg", t));
|
|
69
|
-
const tokenStr = this.message.tokensBefore.toLocaleString();
|
|
70
|
-
const frameCount = this.message.images?.length ?? 0;
|
|
71
|
-
const frameNote =
|
|
72
|
-
frameCount > 0 ? `\n\n_${frameCount} snapcompact frame${frameCount === 1 ? "" : "s"} attached_` : "";
|
|
73
65
|
box.addChild(
|
|
74
|
-
new Markdown(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
0,
|
|
78
|
-
getMarkdownTheme(),
|
|
79
|
-
{
|
|
80
|
-
color: (text: string) => theme.fg("customMessageText", text),
|
|
81
|
-
},
|
|
82
|
-
),
|
|
66
|
+
new Markdown(this.options.detailMarkdown(), 0, 0, getMarkdownTheme(), {
|
|
67
|
+
color: (text: string) => theme.fg("customMessageText", text),
|
|
68
|
+
}),
|
|
83
69
|
);
|
|
84
70
|
this.#detail = box;
|
|
85
71
|
return box;
|
|
86
72
|
}
|
|
87
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compaction point in the transcript, rendered as a slim horizontal divider:
|
|
77
|
+
*
|
|
78
|
+
* ──────── 📷 compacted · ctrl+o ────────
|
|
79
|
+
*
|
|
80
|
+
* The conversation above the divider stays visible (display transcript keeps
|
|
81
|
+
* full history); only the LLM context was reset. Expanding (ctrl+o) reveals
|
|
82
|
+
* the compaction summary below the divider.
|
|
83
|
+
*/
|
|
84
|
+
export class CompactionSummaryMessageComponent implements Component {
|
|
85
|
+
#divider: SummaryDividerComponent;
|
|
86
|
+
|
|
87
|
+
constructor(private readonly message: CompactionSummaryMessage) {
|
|
88
|
+
this.#divider = new SummaryDividerComponent({
|
|
89
|
+
label: () => `${theme.icon.camera} compacted`,
|
|
90
|
+
detailMarkdown: () => this.#detailMarkdown(),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setExpanded(expanded: boolean): void {
|
|
95
|
+
this.#divider.setExpanded(expanded);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
invalidate(): void {
|
|
99
|
+
this.#divider.invalidate();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
render(width: number): readonly string[] {
|
|
103
|
+
return this.#divider.render(width);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#detailMarkdown(): string {
|
|
107
|
+
const tokenStr = this.message.tokensBefore.toLocaleString();
|
|
108
|
+
const frameCount = this.message.images?.length ?? 0;
|
|
109
|
+
const frameNote =
|
|
110
|
+
frameCount > 0 ? `\n\n_${frameCount} snapcompact frame${frameCount === 1 ? "" : "s"} attached_` : "";
|
|
111
|
+
return `**Compacted from ${tokenStr} tokens**\n\n${this.message.summary}${frameNote}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handoff is a compaction strategy too, but it is persisted as a custom message
|
|
117
|
+
* so the LLM sees the handoff-specific developer context. Render it with the
|
|
118
|
+
* same divider affordance as `/compact` instead of the generic `[handoff]` box.
|
|
119
|
+
*/
|
|
120
|
+
export class HandoffSummaryMessageComponent implements Component {
|
|
121
|
+
#divider: SummaryDividerComponent;
|
|
122
|
+
|
|
123
|
+
constructor(private readonly message: CustomMessage<unknown>) {
|
|
124
|
+
this.#divider = new SummaryDividerComponent({
|
|
125
|
+
label: () => `${theme.icon.context} handoff`,
|
|
126
|
+
detailMarkdown: () => this.#detailMarkdown(),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setExpanded(expanded: boolean): void {
|
|
131
|
+
this.#divider.setExpanded(expanded);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
invalidate(): void {
|
|
135
|
+
this.#divider.invalidate();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
render(width: number): readonly string[] {
|
|
139
|
+
return this.#divider.render(width);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#detailMarkdown(): string {
|
|
143
|
+
const document = extractHandoffDocument(getCustomMessageText(this.message));
|
|
144
|
+
return `**Handoff context**\n\n${document || "_No handoff content._"}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createHandoffSummaryMessageComponent(
|
|
149
|
+
message: CustomMessage<unknown>,
|
|
150
|
+
expanded: boolean,
|
|
151
|
+
): HandoffSummaryMessageComponent | undefined {
|
|
152
|
+
if (message.customType !== "handoff" || !message.display) return undefined;
|
|
153
|
+
const component = new HandoffSummaryMessageComponent(message);
|
|
154
|
+
component.setExpanded(expanded);
|
|
155
|
+
return component;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getCustomMessageText(message: CustomMessage<unknown>): string {
|
|
159
|
+
if (typeof message.content === "string") return message.content;
|
|
160
|
+
let firstText: string | undefined;
|
|
161
|
+
let parts: string[] | undefined;
|
|
162
|
+
for (const content of message.content) {
|
|
163
|
+
if (content.type !== "text") continue;
|
|
164
|
+
if (firstText === undefined) {
|
|
165
|
+
firstText = content.text;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (parts === undefined) {
|
|
169
|
+
parts = [firstText];
|
|
170
|
+
}
|
|
171
|
+
parts.push(content.text);
|
|
172
|
+
}
|
|
173
|
+
return parts === undefined ? (firstText ?? "") : parts.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractHandoffDocument(text: string): string {
|
|
177
|
+
const openTag = "<handoff-context>";
|
|
178
|
+
const closeTag = "</handoff-context>";
|
|
179
|
+
const openIndex = text.indexOf(openTag);
|
|
180
|
+
if (openIndex === -1) return text.trim();
|
|
181
|
+
|
|
182
|
+
const contentStart = openIndex + openTag.length;
|
|
183
|
+
const closeIndex = text.indexOf(closeTag, contentStart);
|
|
184
|
+
const document = closeIndex === -1 ? text.slice(contentStart) : text.slice(contentStart, closeIndex);
|
|
185
|
+
return document.trim();
|
|
186
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test";
|
|
2
|
+
import { $ } from "bun";
|
|
3
|
+
import { getEditorTheme, initTheme } from "../theme/theme";
|
|
4
|
+
import { CustomEditor, SPACE_HOLD_RELEASE_MS, SPACE_HOLD_THRESHOLD } from "./custom-editor";
|
|
5
|
+
|
|
6
|
+
function makeEditor() {
|
|
7
|
+
const editor = new CustomEditor(getEditorTheme());
|
|
8
|
+
const events: string[] = [];
|
|
9
|
+
editor.sttHoldEnabled = () => true;
|
|
10
|
+
editor.onSpaceHoldStart = () => events.push("start");
|
|
11
|
+
editor.onSpaceHoldEnd = () => events.push("end");
|
|
12
|
+
return { editor, events };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function holdSpace(editor: CustomEditor, count: number): void {
|
|
16
|
+
for (let i = 0; i < count; i++) editor.handleInput(" ");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function decorateInFreshProcess(text: string): Promise<string> {
|
|
20
|
+
const customEditorUrl = new URL("./custom-editor.ts", import.meta.url).href;
|
|
21
|
+
const script = `
|
|
22
|
+
import { CustomEditor } from ${JSON.stringify(customEditorUrl)};
|
|
23
|
+
const editor = new CustomEditor({});
|
|
24
|
+
process.stdout.write(editor.decorateText(${JSON.stringify(text)}));
|
|
25
|
+
`;
|
|
26
|
+
const child = await $`bun -e ${script}`.quiet().nothrow();
|
|
27
|
+
const stdout = child.stdout.toString();
|
|
28
|
+
const stderr = child.stderr.toString();
|
|
29
|
+
if (child.exitCode !== 0) throw new Error(stderr || stdout || `decorate subprocess exited with ${child.exitCode}`);
|
|
30
|
+
return stdout;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("CustomEditor placeholder decoration", () => {
|
|
34
|
+
it("renders paste placeholders before theme initialization", async () => {
|
|
35
|
+
const output = await decorateInFreshProcess("[Paste #1, +30 lines]");
|
|
36
|
+
expect(output).toBe("[Paste #1, +30 lines]");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders image placeholders before theme initialization", async () => {
|
|
40
|
+
const output = await decorateInFreshProcess("[Image #1]");
|
|
41
|
+
expect(output).toBe("[Image #1]");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("CustomEditor space-hold push-to-talk", () => {
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
await initTheme();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
vi.useRealTimers();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("inserts spaces normally below the hold threshold", () => {
|
|
55
|
+
const { editor, events } = makeEditor();
|
|
56
|
+
holdSpace(editor, SPACE_HOLD_THRESHOLD);
|
|
57
|
+
expect(editor.getText()).toBe(" ".repeat(SPACE_HOLD_THRESHOLD));
|
|
58
|
+
expect(events).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("tracks back the space burst and drives the hold lifecycle", () => {
|
|
62
|
+
vi.useFakeTimers();
|
|
63
|
+
const { editor, events } = makeEditor();
|
|
64
|
+
editor.handleInput("h");
|
|
65
|
+
editor.handleInput("i");
|
|
66
|
+
// Crossing the threshold deletes the optimistically-inserted spaces and starts recording,
|
|
67
|
+
// leaving only the pre-burst text behind.
|
|
68
|
+
holdSpace(editor, SPACE_HOLD_THRESHOLD + 1);
|
|
69
|
+
expect(editor.getText()).toBe("hi");
|
|
70
|
+
expect(events).toEqual(["start"]);
|
|
71
|
+
// Continued auto-repeat while the bar is held is swallowed: no spam, no re-trigger.
|
|
72
|
+
holdSpace(editor, 5);
|
|
73
|
+
expect(editor.getText()).toBe("hi");
|
|
74
|
+
expect(events).toEqual(["start"]);
|
|
75
|
+
// An idle gap with no further repeats means the bar was released -> stop + transcribe.
|
|
76
|
+
vi.advanceTimersByTime(SPACE_HOLD_RELEASE_MS + 1);
|
|
77
|
+
expect(events).toEqual(["start", "end"]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("does not trigger when a non-space breaks the run", () => {
|
|
81
|
+
const { editor, events } = makeEditor();
|
|
82
|
+
holdSpace(editor, SPACE_HOLD_THRESHOLD);
|
|
83
|
+
editor.handleInput("x");
|
|
84
|
+
holdSpace(editor, SPACE_HOLD_THRESHOLD);
|
|
85
|
+
expect(events).toEqual([]);
|
|
86
|
+
expect(editor.getText()).toBe(`${" ".repeat(SPACE_HOLD_THRESHOLD)}x${" ".repeat(SPACE_HOLD_THRESHOLD)}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("leaves the space bar typing normally when the gesture is disabled", () => {
|
|
90
|
+
const { editor, events } = makeEditor();
|
|
91
|
+
editor.sttHoldEnabled = () => false;
|
|
92
|
+
holdSpace(editor, SPACE_HOLD_THRESHOLD + 5);
|
|
93
|
+
expect(editor.getText()).toBe(" ".repeat(SPACE_HOLD_THRESHOLD + 5));
|
|
94
|
+
expect(events).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { addKeyAliases, canonicalKeyId, Editor, type KeyId, parseKey, parseKittySequence } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import type { AppKeybinding } from "../../config/keybindings";
|
|
3
|
+
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
3
4
|
import { imageReferenceHyperlink, PLACEHOLDER_REGEX, renderPlaceholders } from "../image-references";
|
|
4
|
-
import { highlightMagicKeywords } from "../magic-keywords";
|
|
5
|
-
import {
|
|
5
|
+
import { hasMagicKeyword, highlightMagicKeywords } from "../magic-keywords";
|
|
6
|
+
import { fgOrPlain } from "../theme/theme";
|
|
6
7
|
|
|
7
8
|
type ConfigurableEditorAction = Extract<
|
|
8
9
|
AppKeybinding,
|
|
@@ -61,6 +62,14 @@ const BRACKETED_IMAGE_PATH_REGEX = /\.(?:png|jpe?g|gif|webp)$/i;
|
|
|
61
62
|
const BRACKETED_IMAGE_PATH_BOUNDARY_REGEX = /\.(?:png|jpe?g|gif|webp)(?=$|["']?\s)/gi;
|
|
62
63
|
const SHELL_ESCAPED_PATH_CHAR_REGEX = /\\([\\\s'"()[\]{}&;<>|?*!$`])/g;
|
|
63
64
|
|
|
65
|
+
/** Plain spaces from one auto-repeat run that trigger the space-hold push-to-talk STT gesture.
|
|
66
|
+
* Holding the space bar makes the terminal emit a burst of spaces; once more than this many land
|
|
67
|
+
* in the editor we treat it as "space held", track them back out, and start recording. */
|
|
68
|
+
export const SPACE_HOLD_THRESHOLD = 5;
|
|
69
|
+
/** Idle gap (ms) after the last repeated space that counts as the space bar being released, ending
|
|
70
|
+
* the push-to-talk recording. Must comfortably exceed the OS key-repeat interval. */
|
|
71
|
+
export const SPACE_HOLD_RELEASE_MS = 250;
|
|
72
|
+
|
|
64
73
|
function isPastedPathSeparator(char: string | undefined): boolean {
|
|
65
74
|
return char === undefined || char === " " || char === "\t" || char === "\r" || char === "\n";
|
|
66
75
|
}
|
|
@@ -136,19 +145,85 @@ export class CustomEditor extends Editor {
|
|
|
136
145
|
* instead of corrupting `[Paste #1, +30 lines]` into plain text. */
|
|
137
146
|
override atomicTokenPattern = PLACEHOLDER_REGEX;
|
|
138
147
|
|
|
148
|
+
/** Magic-keyword shimmer cadence — drives one editor repaint every 70 ms while
|
|
149
|
+
* a keyword is on screen and the prompt is focused. ~14 frames/s is smooth
|
|
150
|
+
* without flooding the renderer. */
|
|
151
|
+
static readonly SHIMMER_FRAME_MS = 70;
|
|
152
|
+
/** Time for the gradient to sweep one full cycle across each keyword. */
|
|
153
|
+
static readonly SHIMMER_PERIOD_MS = 1800;
|
|
154
|
+
|
|
155
|
+
/** Per-render scratch flag: did any layout line in this render contain a magic
|
|
156
|
+
* keyword that should shimmer? Reset by {@link #scheduleShimmerIfNeeded} each
|
|
157
|
+
* time a frame is queued. */
|
|
158
|
+
#shimmerTimer: ReturnType<typeof setTimeout> | undefined;
|
|
159
|
+
/** Repaint hook the host wires once at construction. Called from the shimmer
|
|
160
|
+
* timer to request the next animation frame. Undefined when nobody is
|
|
161
|
+
* listening (tests, headless callers); the timer chain still self-cleans. */
|
|
162
|
+
#requestShimmerRepaint: (() => void) | undefined;
|
|
163
|
+
|
|
139
164
|
/** Gradient-highlight the "ultrathink" / "orchestrate" / "workflowz" keywords as the user types
|
|
140
165
|
* them, skipping any occurrence inside code spans, fenced blocks, or XML sections. Also make
|
|
141
|
-
* pasted image placeholders visually distinct and hyperlink them once their blob file exists.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
* pasted image placeholders visually distinct and hyperlink them once their blob file exists.
|
|
167
|
+
* When the editor is focused, the buffer contains a magic keyword, and `magicKeywords.enabled`
|
|
168
|
+
* is on, the gradient shifts every frame to produce a Claude-Code-style shimmer; each render
|
|
169
|
+
* schedules the next frame, so losing focus, deleting the keyword, or flipping the setting
|
|
170
|
+
* stops the animation on its own. The static glow itself runs even when shimmering is gated
|
|
171
|
+
* off, matching existing behavior for the editor and sent bubbles. */
|
|
172
|
+
decorateText = (text: string): string => {
|
|
173
|
+
const animated = this.focused && this.#shimmerEnabled() && hasMagicKeyword(this.getText());
|
|
174
|
+
const phase = animated ? (Date.now() % CustomEditor.SHIMMER_PERIOD_MS) / CustomEditor.SHIMMER_PERIOD_MS : 0;
|
|
175
|
+
if (animated) this.#scheduleShimmerFrame();
|
|
176
|
+
return renderPlaceholders(text, {
|
|
177
|
+
renderText: value => highlightMagicKeywords(value, undefined, phase),
|
|
145
178
|
renderReference: (value, kind, index) =>
|
|
146
179
|
kind === "image"
|
|
147
180
|
? imageReferenceHyperlink(value, index, this.imageLinks, label =>
|
|
148
|
-
|
|
181
|
+
fgOrPlain("accent", label, `\x1b[1m\x1b[4m${label}\x1b[24m\x1b[22m`),
|
|
149
182
|
)
|
|
150
|
-
:
|
|
183
|
+
: fgOrPlain("accent", value, `\x1b[1m${value}\x1b[22m`),
|
|
151
184
|
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** Optional test/host override for the magic-keyword shimmer gate. When
|
|
188
|
+
* defined, takes precedence over the global `magicKeywords.enabled` setting,
|
|
189
|
+
* letting tests assert the gating behaviour without mutating the
|
|
190
|
+
* process-wide Settings singleton (which races with parallel test files —
|
|
191
|
+
* see issue #2582). Production wires this through the host's Settings
|
|
192
|
+
* reader and updates it on the relevant setting change. */
|
|
193
|
+
magicKeywordsEnabledOverride: boolean | undefined;
|
|
194
|
+
|
|
195
|
+
/** Whether the shimmer should advance this frame. Defaults to "on" before
|
|
196
|
+
* settings have initialised (tests, early boot) so the animation does not
|
|
197
|
+
* silently disappear during a race; settings disabling the feature wins
|
|
198
|
+
* once they are loaded. An explicit `magicKeywordsEnabledOverride` overrides
|
|
199
|
+
* both paths. */
|
|
200
|
+
#shimmerEnabled(): boolean {
|
|
201
|
+
if (this.magicKeywordsEnabledOverride !== undefined) return this.magicKeywordsEnabledOverride;
|
|
202
|
+
return isSettingsInitialized() ? settings.get("magicKeywords.enabled") : true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Bind the host's render request callback. Idempotent — the host wires this
|
|
206
|
+
* once after construction (and again after `setEditorComponent` swaps the
|
|
207
|
+
* editor). Passing `undefined` clears any pending frame. */
|
|
208
|
+
setShimmerRepaintHandler(handler: (() => void) | undefined): void {
|
|
209
|
+
this.#requestShimmerRepaint = handler;
|
|
210
|
+
if (!handler && this.#shimmerTimer) {
|
|
211
|
+
clearTimeout(this.#shimmerTimer);
|
|
212
|
+
this.#shimmerTimer = undefined;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Schedule one shimmer frame if none is already pending. The next render
|
|
217
|
+
* decides whether to schedule another, so the chain stops by itself when
|
|
218
|
+
* `focused` flips off or the keyword leaves the buffer. */
|
|
219
|
+
#scheduleShimmerFrame(): void {
|
|
220
|
+
if (this.#shimmerTimer || !this.#requestShimmerRepaint) return;
|
|
221
|
+
this.#shimmerTimer = setTimeout(() => {
|
|
222
|
+
this.#shimmerTimer = undefined;
|
|
223
|
+
this.#requestShimmerRepaint?.();
|
|
224
|
+
}, CustomEditor.SHIMMER_FRAME_MS);
|
|
225
|
+
this.#shimmerTimer.unref?.();
|
|
226
|
+
}
|
|
152
227
|
onEscape?: () => void;
|
|
153
228
|
onClear?: () => void;
|
|
154
229
|
onExit?: () => void;
|
|
@@ -178,9 +253,25 @@ export class CustomEditor extends Editor {
|
|
|
178
253
|
/** Called when left-arrow is pressed while the editor is empty (cursor necessarily at start). */
|
|
179
254
|
onLeftAtStart?: () => void;
|
|
180
255
|
|
|
256
|
+
/** Fired when a sustained space-bar hold is recognized — the push-to-talk STT start. The
|
|
257
|
+
* optimistically-typed spaces have already been deleted by the time this runs. */
|
|
258
|
+
onSpaceHoldStart?: () => void;
|
|
259
|
+
/** Fired when the held space bar is released (detected as an idle gap with no further repeated
|
|
260
|
+
* spaces) — the push-to-talk STT stop. */
|
|
261
|
+
onSpaceHoldEnd?: () => void;
|
|
262
|
+
/** Gate for the space-hold gesture. Returns false to keep the space bar inserting spaces
|
|
263
|
+
* normally; wired to `stt.enabled` so disabling STT restores plain space behavior. */
|
|
264
|
+
sttHoldEnabled?: () => boolean;
|
|
265
|
+
|
|
181
266
|
/** Custom key handlers from extensions and non-built-in app actions. */
|
|
182
267
|
#customKeyHandlers = new Map<KeyId, () => void>();
|
|
183
268
|
#customMatchKeys = new Map<string, () => void>();
|
|
269
|
+
/** Consecutive plain spaces inserted in the current run; any other key resets it. */
|
|
270
|
+
#spaceRunInserted = 0;
|
|
271
|
+
/** True while a recognized space-hold push-to-talk recording is in progress. */
|
|
272
|
+
#spaceHoldActive = false;
|
|
273
|
+
/** Idle timer that fires `onSpaceHoldEnd` once repeated spaces stop arriving. */
|
|
274
|
+
#spaceHoldTimer: NodeJS.Timeout | undefined;
|
|
184
275
|
#actionKeys = new Map<ConfigurableEditorAction, KeyId[]>(
|
|
185
276
|
Object.entries(DEFAULT_ACTION_KEYS).map(([action, keys]) => [action as ConfigurableEditorAction, [...keys]]),
|
|
186
277
|
);
|
|
@@ -238,6 +329,68 @@ export class CustomEditor extends Editor {
|
|
|
238
329
|
this.#rebuildCustomMatchKeys();
|
|
239
330
|
}
|
|
240
331
|
|
|
332
|
+
#spaceHoldGestureEnabled(): boolean {
|
|
333
|
+
return this.onSpaceHoldStart !== undefined && (this.sttHoldEnabled?.() ?? false) && !this.isShowingAutocomplete();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Drive the space-hold push-to-talk state machine. Returns true when the gesture consumed the
|
|
337
|
+
* input so it must not reach normal editing. Holding the space bar makes the terminal emit a
|
|
338
|
+
* burst of auto-repeat spaces; once more than {@link SPACE_HOLD_THRESHOLD} of them land we treat
|
|
339
|
+
* it as a hold, delete the spam, and start recording until the repeats stop. */
|
|
340
|
+
#handleSpaceHold(data: string, canonical: string | undefined): boolean {
|
|
341
|
+
const isSpace = canonical === "space";
|
|
342
|
+
if (this.#spaceHoldActive) {
|
|
343
|
+
if (isSpace) {
|
|
344
|
+
// Auto-repeat while held: swallow it and keep the release timer alive.
|
|
345
|
+
this.#armSpaceHoldReleaseTimer();
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
// Any non-space means the bar was released — stop recording, then let the key through.
|
|
349
|
+
this.#endSpaceHold();
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
if (!isSpace) {
|
|
353
|
+
this.#spaceRunInserted = 0;
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
if (!this.#spaceHoldGestureEnabled()) return false;
|
|
357
|
+
// A short tap should still type a normal space, so insert optimistically and count the run.
|
|
358
|
+
super.handleInput(data);
|
|
359
|
+
this.#spaceRunInserted++;
|
|
360
|
+
if (this.#spaceRunInserted > SPACE_HOLD_THRESHOLD) {
|
|
361
|
+
this.deleteBeforeCursor(this.#spaceRunInserted);
|
|
362
|
+
this.#spaceRunInserted = 0;
|
|
363
|
+
this.#beginSpaceHold();
|
|
364
|
+
}
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#beginSpaceHold(): void {
|
|
369
|
+
this.#spaceHoldActive = true;
|
|
370
|
+
this.#armSpaceHoldReleaseTimer();
|
|
371
|
+
this.onSpaceHoldStart?.();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#armSpaceHoldReleaseTimer(): void {
|
|
375
|
+
if (this.#spaceHoldTimer) clearTimeout(this.#spaceHoldTimer);
|
|
376
|
+
this.#spaceHoldTimer = setTimeout(() => {
|
|
377
|
+
this.#spaceHoldTimer = undefined;
|
|
378
|
+
this.#endSpaceHold();
|
|
379
|
+
}, SPACE_HOLD_RELEASE_MS);
|
|
380
|
+
this.#spaceHoldTimer.unref?.();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#endSpaceHold(): void {
|
|
384
|
+
if (!this.#spaceHoldActive) return;
|
|
385
|
+
this.#spaceHoldActive = false;
|
|
386
|
+
this.#spaceRunInserted = 0;
|
|
387
|
+
if (this.#spaceHoldTimer) {
|
|
388
|
+
clearTimeout(this.#spaceHoldTimer);
|
|
389
|
+
this.#spaceHoldTimer = undefined;
|
|
390
|
+
}
|
|
391
|
+
this.onSpaceHoldEnd?.();
|
|
392
|
+
}
|
|
393
|
+
|
|
241
394
|
handleInput(data: string): void {
|
|
242
395
|
const kittyParsed = parseKittySequence(data);
|
|
243
396
|
if (kittyParsed && (kittyParsed.modifier & 64) !== 0 && this.onCapsLock) {
|
|
@@ -267,6 +420,9 @@ export class CustomEditor extends Editor {
|
|
|
267
420
|
return;
|
|
268
421
|
}
|
|
269
422
|
|
|
423
|
+
// Space-hold push-to-talk: a sustained space bar starts/stops STT instead of typing spaces.
|
|
424
|
+
if (this.#handleSpaceHold(data, canonical)) return;
|
|
425
|
+
|
|
270
426
|
if (canonical !== undefined) {
|
|
271
427
|
// Intercept configured image paste (async - fires and handles result)
|
|
272
428
|
if (this.#matchesAction(canonical, "app.clipboard.pasteImage") && this.onPasteImage) {
|
|
@@ -16,6 +16,7 @@ export * from "./hook-message";
|
|
|
16
16
|
export * from "./hook-selector";
|
|
17
17
|
export * from "./keybinding-hints";
|
|
18
18
|
export * from "./login-dialog";
|
|
19
|
+
export * from "./logout-account-selector";
|
|
19
20
|
export * from "./model-selector";
|
|
20
21
|
export * from "./oauth-selector";
|
|
21
22
|
export * from "./queue-mode-selector";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Container, matchesKey, ScrollView, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { theme } from "../../modes/theme/theme";
|
|
3
|
+
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
4
|
+
import type { LogoutAccount } from "../../slash-commands/helpers/logout";
|
|
5
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
6
|
+
|
|
7
|
+
const LOGOUT_SELECTOR_MAX_VISIBLE = 10;
|
|
8
|
+
|
|
9
|
+
/** Account picker for `/logout` after the provider has been selected. */
|
|
10
|
+
export class LogoutAccountSelectorComponent extends Container {
|
|
11
|
+
#listContainer: Container;
|
|
12
|
+
#accounts: LogoutAccount[];
|
|
13
|
+
#selectedIndex = 0;
|
|
14
|
+
#statusMessage: string | undefined;
|
|
15
|
+
#onSelectCallback: (account: LogoutAccount) => void;
|
|
16
|
+
#onCancelCallback: () => void;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
providerName: string,
|
|
20
|
+
accounts: LogoutAccount[],
|
|
21
|
+
onSelect: (account: LogoutAccount) => void,
|
|
22
|
+
onCancel: () => void,
|
|
23
|
+
) {
|
|
24
|
+
super();
|
|
25
|
+
this.#accounts = accounts;
|
|
26
|
+
this.#onSelectCallback = onSelect;
|
|
27
|
+
this.#onCancelCallback = onCancel;
|
|
28
|
+
const activeIndex = accounts.findIndex(account => account.active);
|
|
29
|
+
this.#selectedIndex = activeIndex >= 0 ? activeIndex : 0;
|
|
30
|
+
|
|
31
|
+
this.addChild(new DynamicBorder());
|
|
32
|
+
this.addChild(new Spacer(1));
|
|
33
|
+
this.addChild(new TruncatedText(theme.bold(`Select ${providerName} account to log out:`)));
|
|
34
|
+
this.addChild(new Spacer(1));
|
|
35
|
+
this.#listContainer = new Container();
|
|
36
|
+
this.addChild(this.#listContainer);
|
|
37
|
+
this.addChild(new Spacer(1));
|
|
38
|
+
this.addChild(new DynamicBorder());
|
|
39
|
+
this.#updateList();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#updateList(): void {
|
|
43
|
+
this.#listContainer.clear();
|
|
44
|
+
|
|
45
|
+
const total = this.#accounts.length;
|
|
46
|
+
const maxVisible = LOGOUT_SELECTOR_MAX_VISIBLE;
|
|
47
|
+
const startIndex =
|
|
48
|
+
total <= maxVisible
|
|
49
|
+
? 0
|
|
50
|
+
: Math.max(0, Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
|
|
51
|
+
const endIndex = Math.min(startIndex + maxVisible, total);
|
|
52
|
+
|
|
53
|
+
const rows: string[] = [];
|
|
54
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
55
|
+
const account = this.#accounts[i];
|
|
56
|
+
if (!account) continue;
|
|
57
|
+
const activeTag = account.active ? theme.fg("muted", " (active)") : "";
|
|
58
|
+
const detail = account.detail ? theme.fg("dim", ` ${account.detail}`) : "";
|
|
59
|
+
if (i === this.#selectedIndex) {
|
|
60
|
+
rows.push(`${theme.fg("accent", `${theme.nav.cursor} ${account.label}`)}${activeTag}${detail}`);
|
|
61
|
+
} else {
|
|
62
|
+
rows.push(` ${account.label}${activeTag}${detail}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (rows.length > 0) {
|
|
67
|
+
const sv = new ScrollView(rows, {
|
|
68
|
+
height: rows.length,
|
|
69
|
+
scrollbar: "auto",
|
|
70
|
+
totalRows: total,
|
|
71
|
+
theme: { track: text => theme.fg("muted", text), thumb: text => theme.fg("accent", text) },
|
|
72
|
+
});
|
|
73
|
+
sv.setScrollOffset(startIndex);
|
|
74
|
+
this.#listContainer.addChild(sv);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (total === 0) {
|
|
78
|
+
this.#listContainer.addChild(new TruncatedText(theme.fg("muted", " No stored accounts to log out"), 0, 0));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.#listContainer.addChild(
|
|
82
|
+
new TruncatedText(theme.fg("muted", " ↑/↓ select · ↵ log out account · Esc cancel"), 0, 0),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (this.#statusMessage) {
|
|
86
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
87
|
+
this.#listContainer.addChild(new TruncatedText(theme.fg("warning", ` ${this.#statusMessage}`), 0, 0));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
handleInput(keyData: string): void {
|
|
92
|
+
if (matchesSelectCancel(keyData)) {
|
|
93
|
+
this.#onCancelCallback();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (matchesSelectUp(keyData)) {
|
|
98
|
+
if (this.#accounts.length > 0) {
|
|
99
|
+
this.#selectedIndex = this.#selectedIndex === 0 ? this.#accounts.length - 1 : this.#selectedIndex - 1;
|
|
100
|
+
}
|
|
101
|
+
this.#statusMessage = undefined;
|
|
102
|
+
this.#updateList();
|
|
103
|
+
} else if (matchesSelectDown(keyData)) {
|
|
104
|
+
if (this.#accounts.length > 0) {
|
|
105
|
+
this.#selectedIndex = this.#selectedIndex === this.#accounts.length - 1 ? 0 : this.#selectedIndex + 1;
|
|
106
|
+
}
|
|
107
|
+
this.#statusMessage = undefined;
|
|
108
|
+
this.#updateList();
|
|
109
|
+
} else if (matchesKey(keyData, "pageUp")) {
|
|
110
|
+
if (this.#accounts.length > 0) {
|
|
111
|
+
this.#selectedIndex = Math.max(0, this.#selectedIndex - LOGOUT_SELECTOR_MAX_VISIBLE);
|
|
112
|
+
}
|
|
113
|
+
this.#statusMessage = undefined;
|
|
114
|
+
this.#updateList();
|
|
115
|
+
} else if (matchesKey(keyData, "pageDown")) {
|
|
116
|
+
if (this.#accounts.length > 0) {
|
|
117
|
+
this.#selectedIndex = Math.min(
|
|
118
|
+
this.#accounts.length - 1,
|
|
119
|
+
this.#selectedIndex + LOGOUT_SELECTOR_MAX_VISIBLE,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
this.#statusMessage = undefined;
|
|
123
|
+
this.#updateList();
|
|
124
|
+
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
125
|
+
const account = this.#accounts[this.#selectedIndex];
|
|
126
|
+
if (!account) return;
|
|
127
|
+
this.#onSelectCallback(account);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -262,7 +262,7 @@ export class MCPAddWizard extends Container {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
this.#contentContainer.addChild(
|
|
265
|
-
new Text(theme.fg("muted", "[Only letters, numbers, dash, underscore, dot]"), 0, 0),
|
|
265
|
+
new Text(theme.fg("muted", "[Only letters, numbers, dash, underscore, dot, colon]"), 0, 0),
|
|
266
266
|
);
|
|
267
267
|
this.#contentContainer.addChild(new Text(theme.fg("muted", "[Enter to continue, Esc to cancel]"), 0, 0));
|
|
268
268
|
}
|
|
@@ -711,7 +711,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
711
711
|
if (!this.#isModelOverContextLimit(model)) {
|
|
712
712
|
return "";
|
|
713
713
|
}
|
|
714
|
-
return ` ${theme.status.disabled} context>${formatNumber(model.contextWindow).toLowerCase()}`;
|
|
714
|
+
return ` ${theme.status.disabled} context>${formatNumber(model.contextWindow ?? 0).toLowerCase()}`;
|
|
715
715
|
}
|
|
716
716
|
|
|
717
717
|
#getVisibleItems(): ReadonlyArray<ModelItem | CanonicalModelItem> {
|
|
@@ -1016,7 +1016,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1016
1016
|
const limitWarning = this.#isItemDisabled(selected)
|
|
1017
1017
|
? theme.fg(
|
|
1018
1018
|
"dim",
|
|
1019
|
-
` — current context ${formatNumber(this.#currentContextTokens).toLowerCase()} > ${formatNumber(selected.model.contextWindow).toLowerCase()} limit`,
|
|
1019
|
+
` — current context ${formatNumber(this.#currentContextTokens).toLowerCase()} > ${formatNumber(selected.model.contextWindow ?? 0).toLowerCase()} limit`,
|
|
1020
1020
|
)
|
|
1021
1021
|
: "";
|
|
1022
1022
|
this.#listContainer.addChild(
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
16
16
|
import { theme } from "../../modes/theme/theme";
|
|
17
17
|
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
18
|
-
import type { SessionInfo, SessionStatus } from "../../session/session-
|
|
18
|
+
import type { SessionInfo, SessionStatus } from "../../session/session-listing";
|
|
19
19
|
import { shortenPath } from "../../tools/render-utils";
|
|
20
20
|
import { DynamicBorder } from "./dynamic-border";
|
|
21
21
|
import { HookSelectorComponent } from "./hook-selector";
|