@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
package/src/async/job-manager.ts
CHANGED
|
@@ -6,6 +6,27 @@ const DELIVERY_RETRY_JITTER_MS = 200;
|
|
|
6
6
|
const DEFAULT_RETENTION_MS = 5 * 60 * 1000;
|
|
7
7
|
const DEFAULT_MAX_RUNNING_JOBS = 15;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Adaptive ("smart") `job` poll-wait ladder (ms). A tight poll loop climbs
|
|
11
|
+
* these rungs so each immediate re-poll backs off and stops spending turns on
|
|
12
|
+
* "still running" frames; the floor (first rung) is the shortest wait and the
|
|
13
|
+
* top rung is the longest a smart poll will ever block. Only used when
|
|
14
|
+
* `async.pollWaitDuration` is set to `smart`; fixed durations wait verbatim.
|
|
15
|
+
*/
|
|
16
|
+
const POLL_WAIT_LADDER_MS = [5_000, 10_000, 30_000, 60_000, 300_000] as const;
|
|
17
|
+
/**
|
|
18
|
+
* Going at least this long between poll calls means the agent stepped out of
|
|
19
|
+
* the poll loop to do real work — the next poll drops back to the ladder floor.
|
|
20
|
+
*/
|
|
21
|
+
const POLL_ESCALATION_RESET_MS = 60_000;
|
|
22
|
+
|
|
23
|
+
interface PollEscalationState {
|
|
24
|
+
/** Index into POLL_WAIT_LADDER_MS used for the most recent poll wait. */
|
|
25
|
+
level: number;
|
|
26
|
+
/** Timestamp (ms) when the most recent poll wait returned. */
|
|
27
|
+
lastPollEndAt: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
export interface AsyncJob {
|
|
10
31
|
id: string;
|
|
11
32
|
type: "bash" | "task";
|
|
@@ -96,6 +117,7 @@ export class AsyncJobManager {
|
|
|
96
117
|
readonly #suppressedDeliveries = new Set<string>();
|
|
97
118
|
readonly #watchedJobs = new Set<string>();
|
|
98
119
|
readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
|
|
120
|
+
readonly #pollEscalation = new Map<string | undefined, PollEscalationState>();
|
|
99
121
|
readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
|
|
100
122
|
readonly #maxRunningJobs: number;
|
|
101
123
|
readonly #retentionMs: number;
|
|
@@ -295,6 +317,32 @@ export class AsyncJobManager {
|
|
|
295
317
|
return removed;
|
|
296
318
|
}
|
|
297
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Compute the next adaptive ("smart") wait (ms) for a blocking `job` poll by
|
|
322
|
+
* the given owner. Consecutive polls — those starting within
|
|
323
|
+
* POLL_ESCALATION_RESET_MS of the previous poll returning — climb
|
|
324
|
+
* POLL_WAIT_LADDER_MS so a tight wait loop backs off; a longer gap means the
|
|
325
|
+
* agent left to do real work, so the wait resets to the floor. Pair each call
|
|
326
|
+
* with `recordPollWaitEnd()` once the wait returns.
|
|
327
|
+
*/
|
|
328
|
+
nextPollWaitMs(ownerId: string | undefined, now: number = Date.now()): number {
|
|
329
|
+
const prev = this.#pollEscalation.get(ownerId);
|
|
330
|
+
const reset = !prev || now - prev.lastPollEndAt >= POLL_ESCALATION_RESET_MS;
|
|
331
|
+
const level = reset ? 0 : Math.min(prev.level + 1, POLL_WAIT_LADDER_MS.length - 1);
|
|
332
|
+
this.#pollEscalation.set(ownerId, { level, lastPollEndAt: prev?.lastPollEndAt ?? now });
|
|
333
|
+
return POLL_WAIT_LADDER_MS[level];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Mark a blocking poll wait as finished so the idle-reset window is measured
|
|
338
|
+
* from now. Polling again before POLL_ESCALATION_RESET_MS elapses keeps
|
|
339
|
+
* climbing the ladder; waiting longer resets it to the floor.
|
|
340
|
+
*/
|
|
341
|
+
recordPollWaitEnd(ownerId: string | undefined, now: number = Date.now()): void {
|
|
342
|
+
const prev = this.#pollEscalation.get(ownerId);
|
|
343
|
+
this.#pollEscalation.set(ownerId, { level: prev?.level ?? 0, lastPollEndAt: now });
|
|
344
|
+
}
|
|
345
|
+
|
|
298
346
|
acknowledgeDeliveries(jobIds: string[]): number {
|
|
299
347
|
const uniqueJobIds = Array.from(new Set(jobIds.map(id => id.trim()).filter(id => id.length > 0)));
|
|
300
348
|
if (uniqueJobIds.length === 0) return 0;
|
|
@@ -405,6 +453,7 @@ export class AsyncJobManager {
|
|
|
405
453
|
this.#inFlightDeliveries.length = 0;
|
|
406
454
|
this.#suppressedDeliveries.clear();
|
|
407
455
|
this.#watchedJobs.clear();
|
|
456
|
+
this.#pollEscalation.clear();
|
|
408
457
|
return drained;
|
|
409
458
|
}
|
|
410
459
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-learn session controller (experimental).
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to the session event stream and, after a substantive turn,
|
|
5
|
+
* nudges the agent to capture reusable lessons. Default posture is passive
|
|
6
|
+
* (a hidden reminder rides the next real turn); with `autolearn.autoContinue`
|
|
7
|
+
* it auto-runs exactly one synthetic capture turn at stop.
|
|
8
|
+
*
|
|
9
|
+
* Installed once per top-level session (taskDepth 0). The subscription lives
|
|
10
|
+
* for the session's lifetime — `newSession` resets the session in place
|
|
11
|
+
* without re-running startup — so the controller needs no disposal.
|
|
12
|
+
*/
|
|
13
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
14
|
+
import type { Settings } from "../config/settings";
|
|
15
|
+
import autolearnGuidance from "../prompts/system/autolearn-guidance.md" with { type: "text" };
|
|
16
|
+
import autolearnGuidanceLearn from "../prompts/system/autolearn-guidance-learn.md" with { type: "text" };
|
|
17
|
+
import autolearnNudge from "../prompts/system/autolearn-nudge.md" with { type: "text" };
|
|
18
|
+
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
19
|
+
|
|
20
|
+
const AUTOLEARN_NUDGE = autolearnNudge.trim();
|
|
21
|
+
const DEFAULT_MIN_TOOL_CALLS = 5;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build the standing auto-learn guidance for the system prompt from the tools
|
|
25
|
+
* actually present in the active set, or null when `manage_skill` is absent.
|
|
26
|
+
*
|
|
27
|
+
* Driven by tool presence rather than live settings: the `learn`/`manage_skill`
|
|
28
|
+
* registry is built ONCE at session start (and only for top-level sessions), so
|
|
29
|
+
* keying the guidance on `autolearn.enabled` would let a mid-session enable — or
|
|
30
|
+
* a subagent that filtered the tools out — inject guidance pointing at tools the
|
|
31
|
+
* session never built. The `learn` addendum is included only when the `learn`
|
|
32
|
+
* tool is present (it requires a memory backend).
|
|
33
|
+
*/
|
|
34
|
+
export function buildAutoLearnInstructions(available: { manageSkill: boolean; learn: boolean }): string | null {
|
|
35
|
+
if (!available.manageSkill) return null;
|
|
36
|
+
const parts = [autolearnGuidance.trim()];
|
|
37
|
+
if (available.learn) parts.push(autolearnGuidanceLearn.trim());
|
|
38
|
+
return parts.join("\n\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AutoLearnControllerOptions {
|
|
42
|
+
session: AgentSession;
|
|
43
|
+
settings: Settings;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class AutoLearnController {
|
|
47
|
+
readonly #session: AgentSession;
|
|
48
|
+
readonly #settings: Settings;
|
|
49
|
+
#toolCalls = 0;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the in-flight turn BEGAN while goal mode was active. Captured at
|
|
52
|
+
* agent_start because a `goal` tool can complete or drop the goal mid-turn,
|
|
53
|
+
* clearing the live flag before agent_end — so the end-of-turn state alone
|
|
54
|
+
* would let a goal-continuation turn slip through and get nudged.
|
|
55
|
+
*/
|
|
56
|
+
#turnStartedInGoalMode = false;
|
|
57
|
+
/** Swallow the agent_end produced by an auto-run capture turn so it cannot re-trigger. */
|
|
58
|
+
#suppressNext = false;
|
|
59
|
+
|
|
60
|
+
constructor(options: AutoLearnControllerOptions) {
|
|
61
|
+
this.#session = options.session;
|
|
62
|
+
this.#settings = options.settings;
|
|
63
|
+
// The listener closure captures `this`, so the session's listener array
|
|
64
|
+
// keeps the controller alive — no stored unsubscribe needed.
|
|
65
|
+
this.#session.subscribe(event => this.#onEvent(event));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#onEvent(event: AgentSessionEvent): void {
|
|
69
|
+
if (event.type === "agent_start") {
|
|
70
|
+
// Capture goal-mode state at the turn boundary, before any tool runs.
|
|
71
|
+
this.#turnStartedInGoalMode = this.#session.getGoalModeState()?.enabled === true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (event.type === "tool_execution_end") {
|
|
75
|
+
this.#toolCalls++;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (event.type === "agent_end") {
|
|
79
|
+
this.#onAgentEnd();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#onAgentEnd(): void {
|
|
84
|
+
// Snapshot and reset every turn: the counter describes only the
|
|
85
|
+
// just-finished turn, so below-threshold, disabled, and plan-mode stops
|
|
86
|
+
// must not let tool calls accumulate into a later turn.
|
|
87
|
+
const toolCalls = this.#toolCalls;
|
|
88
|
+
this.#toolCalls = 0;
|
|
89
|
+
// Snapshot the turn-start goal flag alongside the counter so a turn that
|
|
90
|
+
// observed no agent_start can never inherit a stale value.
|
|
91
|
+
const startedInGoalMode = this.#turnStartedInGoalMode;
|
|
92
|
+
this.#turnStartedInGoalMode = false;
|
|
93
|
+
|
|
94
|
+
if (this.#suppressNext) {
|
|
95
|
+
this.#suppressNext = false;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Honor a live opt-out: the subscription outlives the setting, so re-check
|
|
99
|
+
// the current flag rather than trusting install-time state.
|
|
100
|
+
if (!this.#settings.get("autolearn.enabled")) return;
|
|
101
|
+
const minToolCalls = this.#settings.get("autolearn.minToolCalls") ?? DEFAULT_MIN_TOOL_CALLS;
|
|
102
|
+
if (toolCalls < minToolCalls) return;
|
|
103
|
+
// Never interrupt plan-mode review.
|
|
104
|
+
if (this.#session.getPlanModeState()?.enabled) return;
|
|
105
|
+
// Never divert a goal loop. Skip when the turn STARTED in goal mode — a
|
|
106
|
+
// `goal` tool may have completed/dropped the goal before this stop — or is
|
|
107
|
+
// still in it: a passive nudge would ride the goal continuation, and
|
|
108
|
+
// auto-continue would compete with it.
|
|
109
|
+
if (startedInGoalMode || this.#session.getGoalModeState()?.enabled) return;
|
|
110
|
+
|
|
111
|
+
// Auto-run a capture turn only when explicitly enabled; otherwise the
|
|
112
|
+
// hidden reminder rides the next real turn passively.
|
|
113
|
+
const autoContinue = this.#settings.get("autolearn.autoContinue") === true;
|
|
114
|
+
// Arm suppression synchronously: the synthetic capture turn's agent_end
|
|
115
|
+
// fires inside sendCustomMessage (before it resolves), so the flag must be
|
|
116
|
+
// set before then. Disarm when no turn actually started — a deferred/queued
|
|
117
|
+
// dispatch or a failed send produces no agent_end, and a latched flag would
|
|
118
|
+
// otherwise swallow the next real stop.
|
|
119
|
+
if (autoContinue) this.#suppressNext = true;
|
|
120
|
+
|
|
121
|
+
this.#session
|
|
122
|
+
.sendCustomMessage(
|
|
123
|
+
{
|
|
124
|
+
customType: "autolearn-nudge",
|
|
125
|
+
content: AUTOLEARN_NUDGE,
|
|
126
|
+
display: false,
|
|
127
|
+
attribution: "user",
|
|
128
|
+
},
|
|
129
|
+
{ deliverAs: "nextTurn", triggerTurn: autoContinue },
|
|
130
|
+
)
|
|
131
|
+
.then(started => {
|
|
132
|
+
if (!started) this.#suppressNext = false;
|
|
133
|
+
})
|
|
134
|
+
.catch(err => {
|
|
135
|
+
this.#suppressNext = false;
|
|
136
|
+
logger.warn("auto-learn nudge delivery failed", { err });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed-skills primitives for the experimental auto-learn feature.
|
|
3
|
+
*
|
|
4
|
+
* Managed skills are auto-generated/enhanced `SKILL.md` files kept in an
|
|
5
|
+
* isolated directory (`~/.omp/agent/managed-skills`) separate from
|
|
6
|
+
* user-authored skills (`~/.omp/agent/skills`). They are discovered and
|
|
7
|
+
* surfaced like normal skills, but every write here is confined to
|
|
8
|
+
* `getManagedSkillsDir()` — auto-management can never touch authored skills.
|
|
9
|
+
*/
|
|
10
|
+
import { constants as fsConstants, type Stats } from "node:fs";
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
15
|
+
import { YAML } from "bun";
|
|
16
|
+
import { SOURCE_PATHS } from "../discovery/helpers";
|
|
17
|
+
|
|
18
|
+
/** Provider id stamped on discovered managed skills (distinguishes them from authored). */
|
|
19
|
+
export const MANAGED_SKILLS_PROVIDER_ID = "omp-managed";
|
|
20
|
+
|
|
21
|
+
/** Hard cap on a managed SKILL.md body to keep generated skills bounded. */
|
|
22
|
+
export const MAX_MANAGED_SKILL_BYTES = 64_000;
|
|
23
|
+
|
|
24
|
+
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
25
|
+
|
|
26
|
+
/** Resolve the isolated managed-skills directory (`~/.omp/agent/managed-skills`). */
|
|
27
|
+
export function getManagedSkillsDir(home: string = os.homedir()): string {
|
|
28
|
+
return path.join(home, SOURCE_PATHS.native.userAgent, "managed-skills");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate + normalize a managed-skill name. Throws on anything outside the
|
|
33
|
+
* strict allowlist so a bad name can never escape `getManagedSkillsDir()`
|
|
34
|
+
* (blocks `..`, slashes, empty, and uppercase).
|
|
35
|
+
*/
|
|
36
|
+
export function sanitizeSkillName(raw: string): string {
|
|
37
|
+
const name = raw.trim().toLowerCase();
|
|
38
|
+
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid skill name "${raw}". Use lowercase letters, digits, and hyphens (1-64 chars, starting with a letter or digit).`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether `name` is a safe managed-skill name (the exact post-sanitize shape).
|
|
48
|
+
* Used to validate names read from disk at discovery time — a managed
|
|
49
|
+
* `SKILL.md` whose `frontmatter.name` was not produced by `sanitizeSkillName`
|
|
50
|
+
* (e.g. hand-placed) must not render unescaped into the system prompt.
|
|
51
|
+
*/
|
|
52
|
+
export function isValidManagedSkillName(name: string): boolean {
|
|
53
|
+
return SKILL_NAME_PATTERN.test(name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Neutralize a machine-generated managed-skill description so it cannot break
|
|
58
|
+
* out of the system prompt's `<skills>` listing. Managed descriptions are
|
|
59
|
+
* generated from prior task content and persist across sessions, so this is a
|
|
60
|
+
* trust boundary: strip control/format chars, angle brackets (`<system-directive>`
|
|
61
|
+
* / `</skills>`), and Markdown fence delimiters (backticks, `~~~`), then collapse
|
|
62
|
+
* to a single line. Applied on BOTH write and read so existing files are safe too.
|
|
63
|
+
*/
|
|
64
|
+
export function sanitizeManagedDescription(raw: string): string {
|
|
65
|
+
return raw
|
|
66
|
+
.replace(/[\p{Cc}\p{Cf}]/gu, " ")
|
|
67
|
+
.replace(/[<>`]/g, "")
|
|
68
|
+
.replace(/~{2,}/g, "~")
|
|
69
|
+
.replace(/\s+/g, " ")
|
|
70
|
+
.trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Serialize the minimal `name`/`description` frontmatter block via the repo's
|
|
75
|
+
* YAML helper (round-trips through `parseFrontmatter`).
|
|
76
|
+
*/
|
|
77
|
+
export function toSkillFrontmatter(name: string, description: string): string {
|
|
78
|
+
const frontmatter = YAML.stringify(
|
|
79
|
+
{ name, description: sanitizeManagedDescription(description) },
|
|
80
|
+
null,
|
|
81
|
+
2,
|
|
82
|
+
).trimEnd();
|
|
83
|
+
return `---\n${frontmatter}\n---\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface WriteManagedSkillInput {
|
|
87
|
+
action: "create" | "update";
|
|
88
|
+
name: string;
|
|
89
|
+
description: string;
|
|
90
|
+
body: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Serialize create/update/delete on the same skill name. Both tools are
|
|
95
|
+
* non-exclusive, so a parallel tool batch in one turn can run two mutations on
|
|
96
|
+
* the same skill at once (e.g. an update observing the file mid-delete). This
|
|
97
|
+
* per-name promise chain runs same-skill mutations in submission order while
|
|
98
|
+
* different names still proceed in parallel. In-process only; cross-process
|
|
99
|
+
* races are out of scope.
|
|
100
|
+
*/
|
|
101
|
+
const skillMutationChains = new Map<string, Promise<unknown>>();
|
|
102
|
+
function serializeSkillMutation<T>(name: string, op: () => Promise<T>): Promise<T> {
|
|
103
|
+
const prev = skillMutationChains.get(name) ?? Promise.resolve();
|
|
104
|
+
const run = prev.then(op, op);
|
|
105
|
+
const guarded = run.catch(() => {});
|
|
106
|
+
skillMutationChains.set(name, guarded);
|
|
107
|
+
void guarded.finally(() => {
|
|
108
|
+
if (skillMutationChains.get(name) === guarded) skillMutationChains.delete(name);
|
|
109
|
+
});
|
|
110
|
+
return run;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reject when the managed-skills root itself is a symlink. lstat on a child
|
|
115
|
+
* follows intermediate components, so a symlinked root would let an otherwise
|
|
116
|
+
* valid name write/delete outside the isolated directory (e.g. onto authored
|
|
117
|
+
* skills). Checked before composing any child path.
|
|
118
|
+
*/
|
|
119
|
+
async function assertManagedRootSafe(): Promise<void> {
|
|
120
|
+
const rootStat = await fs.lstat(getManagedSkillsDir()).catch(err => {
|
|
121
|
+
if (isEnoent(err)) return null;
|
|
122
|
+
throw err;
|
|
123
|
+
});
|
|
124
|
+
if (rootStat?.isSymbolicLink()) {
|
|
125
|
+
throw new Error("The managed-skills root is a symlink; refusing to operate outside the managed directory.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const UPDATE_FILE_OPEN_FLAGS = fsConstants.O_WRONLY | fsConstants.O_NOFOLLOW;
|
|
130
|
+
|
|
131
|
+
function assertManagedSkillFileSafeForUpdate(name: string, fileStat: Stats): void {
|
|
132
|
+
if (!fileStat.isFile()) {
|
|
133
|
+
throw new Error(`Managed skill "${name}" SKILL.md is not a regular file; refusing to overwrite it.`);
|
|
134
|
+
}
|
|
135
|
+
if (fileStat.nlink > 1) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Managed skill "${name}" SKILL.md has ${fileStat.nlink} hard links; refusing to overwrite a file that may be user-authored elsewhere.`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function openManagedSkillFileForUpdate(name: string, file: string) {
|
|
143
|
+
try {
|
|
144
|
+
return await fs.open(file, UPDATE_FILE_OPEN_FLAGS);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if ((err as { code?: string }).code === "ELOOP") {
|
|
147
|
+
throw new Error(`Managed skill "${name}" SKILL.md is a symlink; refusing to overwrite it.`);
|
|
148
|
+
}
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Create or update a managed `SKILL.md`. Returns the resolved file path. */
|
|
154
|
+
export async function writeManagedSkill(input: WriteManagedSkillInput): Promise<{ path: string }> {
|
|
155
|
+
const name = sanitizeSkillName(input.name);
|
|
156
|
+
const description = sanitizeManagedDescription(input.description);
|
|
157
|
+
const body = input.body.trim();
|
|
158
|
+
// Reject empty content: an all-whitespace/control description sanitizes to ""
|
|
159
|
+
// and the `requireDescription` discovery scan then silently drops the skill,
|
|
160
|
+
// so the tool would report success for a skill that never appears.
|
|
161
|
+
if (!description) {
|
|
162
|
+
throw new Error(`Managed skill "${name}" needs a non-empty description.`);
|
|
163
|
+
}
|
|
164
|
+
if (!body) {
|
|
165
|
+
throw new Error(`Managed skill "${name}" needs a non-empty body.`);
|
|
166
|
+
}
|
|
167
|
+
const content = `${toSkillFrontmatter(name, description)}\n${body}\n`;
|
|
168
|
+
// Cap the UTF-8 byte size of the FINAL file (body + description + frontmatter),
|
|
169
|
+
// not the UTF-16 code-unit length of the body alone.
|
|
170
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
171
|
+
if (bytes > MAX_MANAGED_SKILL_BYTES) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Managed skill is ${bytes} bytes; the limit is ${MAX_MANAGED_SKILL_BYTES}. Trim the body or description.`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return serializeSkillMutation(name, async () => {
|
|
177
|
+
await assertManagedRootSafe();
|
|
178
|
+
const dir = path.join(getManagedSkillsDir(), name);
|
|
179
|
+
const file = path.join(dir, "SKILL.md");
|
|
180
|
+
// Reject a symlinked skill directory: an intermediate symlink would let the
|
|
181
|
+
// write escape the isolated managed root. lstat does not follow the final
|
|
182
|
+
// component, so a symlinked `dir` is caught here.
|
|
183
|
+
const dirStat = await fs.lstat(dir).catch(err => {
|
|
184
|
+
if (isEnoent(err)) return null;
|
|
185
|
+
throw err;
|
|
186
|
+
});
|
|
187
|
+
if (dirStat?.isSymbolicLink()) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Managed skill "${name}" resolves through a symlink; refusing to write outside the managed directory.`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (input.action === "create") {
|
|
193
|
+
await fs.mkdir(dir, { recursive: true });
|
|
194
|
+
// O_CREAT|O_EXCL ("wx"): atomic create that fails if the file already
|
|
195
|
+
// exists (closing the check-then-write race) and refuses a symlinked SKILL.md.
|
|
196
|
+
try {
|
|
197
|
+
await fs.writeFile(file, content, { flag: "wx" });
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if ((err as { code?: string }).code === "EEXIST") {
|
|
200
|
+
throw new Error(`Managed skill "${name}" already exists. Use action "update" to change it.`);
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
return { path: file };
|
|
205
|
+
}
|
|
206
|
+
// update: the file must already exist, be a plain managed file, and must
|
|
207
|
+
// not share an inode with a user-authored file via hard link. Open the
|
|
208
|
+
// checked file handle before truncating so a path swap after lstat cannot
|
|
209
|
+
// redirect the write into a symlink or newly hard-linked target.
|
|
210
|
+
const fileStat = await fs.lstat(file).catch(err => {
|
|
211
|
+
if (isEnoent(err)) return null;
|
|
212
|
+
throw err;
|
|
213
|
+
});
|
|
214
|
+
if (fileStat === null) {
|
|
215
|
+
throw new Error(`Managed skill "${name}" does not exist. Use action "create" to add it.`);
|
|
216
|
+
}
|
|
217
|
+
if (fileStat.isSymbolicLink()) {
|
|
218
|
+
throw new Error(`Managed skill "${name}" SKILL.md is a symlink; refusing to overwrite it.`);
|
|
219
|
+
}
|
|
220
|
+
assertManagedSkillFileSafeForUpdate(name, fileStat);
|
|
221
|
+
const handle = await openManagedSkillFileForUpdate(name, file);
|
|
222
|
+
try {
|
|
223
|
+
const openStat = await handle.stat();
|
|
224
|
+
assertManagedSkillFileSafeForUpdate(name, openStat);
|
|
225
|
+
await handle.truncate(0);
|
|
226
|
+
await handle.writeFile(content);
|
|
227
|
+
} finally {
|
|
228
|
+
await handle.close();
|
|
229
|
+
}
|
|
230
|
+
return { path: file };
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Delete a managed skill directory. Throws when it does not exist. */
|
|
235
|
+
export async function deleteManagedSkill(name: string): Promise<void> {
|
|
236
|
+
const safe = sanitizeSkillName(name);
|
|
237
|
+
await serializeSkillMutation(safe, async () => {
|
|
238
|
+
await assertManagedRootSafe();
|
|
239
|
+
const dir = path.join(getManagedSkillsDir(), safe);
|
|
240
|
+
// Refuse to follow a symlinked skill directory (rm would delete the target).
|
|
241
|
+
const dirStat = await fs.lstat(dir).catch(err => {
|
|
242
|
+
if (isEnoent(err)) return null;
|
|
243
|
+
throw err;
|
|
244
|
+
});
|
|
245
|
+
if (dirStat?.isSymbolicLink()) {
|
|
246
|
+
throw new Error(`Managed skill "${safe}" is a symlink; refusing to delete outside the managed directory.`);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
await fs.rm(dir, { recursive: true });
|
|
250
|
+
} catch (err) {
|
|
251
|
+
if (isEnoent(err)) {
|
|
252
|
+
throw new Error(`Managed skill "${safe}" does not exist.`);
|
|
253
|
+
}
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
@@ -194,7 +194,6 @@ const SCHEMA_VERSION = 1;
|
|
|
194
194
|
const SCHEMA_SQL = `
|
|
195
195
|
PRAGMA journal_mode=WAL;
|
|
196
196
|
PRAGMA synchronous=NORMAL;
|
|
197
|
-
PRAGMA busy_timeout=5000;
|
|
198
197
|
PRAGMA foreign_keys=ON;
|
|
199
198
|
|
|
200
199
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -263,6 +262,8 @@ export class AutoresearchStorage {
|
|
|
263
262
|
this.#projectDir = projectDir;
|
|
264
263
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
265
264
|
this.#db = new Database(dbPath);
|
|
265
|
+
// Install the busy handler BEFORE any lock-taking statement. See #2421.
|
|
266
|
+
this.#db.run("PRAGMA busy_timeout = 5000");
|
|
266
267
|
this.#db.run(SCHEMA_SQL);
|
|
267
268
|
const versionRow = this.#db.query("PRAGMA user_version").get() as { user_version: number } | null;
|
|
268
269
|
const currentVersion = versionRow?.user_version ?? 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
|
-
import
|
|
4
|
+
import { z } from "zod/v4";
|
|
5
5
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
6
6
|
import type { Theme } from "../../modes/theme/theme";
|
|
7
7
|
import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import
|
|
5
|
+
import { z } from "zod/v4";
|
|
6
6
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
7
7
|
import type { Theme } from "../../modes/theme/theme";
|
|
8
8
|
import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import
|
|
5
|
+
import { z } from "zod/v4";
|
|
6
6
|
import { executeBash } from "../../exec/bash-executor";
|
|
7
7
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
8
8
|
import type { Theme } from "../../modes/theme/theme";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
3
|
import type { ToolDefinition } from "../../extensibility/extensions";
|
|
4
4
|
import type { Theme } from "../../modes/theme/theme";
|
|
5
5
|
import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "../extensibility/extensions";
|
|
3
|
-
import type { SessionEntry } from "../session/session-
|
|
3
|
+
import type { SessionEntry } from "../session/session-entries";
|
|
4
4
|
import type { TruncationResult } from "../session/streaming-output";
|
|
5
5
|
|
|
6
6
|
export type MetricDirection = "lower" | "higher";
|
package/src/cli/args.ts
CHANGED
|
@@ -48,14 +48,24 @@ export interface Args {
|
|
|
48
48
|
noSkills?: boolean;
|
|
49
49
|
skills?: string[];
|
|
50
50
|
noRules?: boolean;
|
|
51
|
-
listModels?: string | true;
|
|
52
51
|
noTitle?: boolean;
|
|
53
52
|
autoApprove?: boolean;
|
|
54
53
|
approvalMode?: "always-ask" | "write" | "yolo";
|
|
55
54
|
messages: string[];
|
|
56
55
|
fileArgs: string[];
|
|
57
|
-
/**
|
|
56
|
+
/** Extension-registered flags this parse recognized — name to value. */
|
|
58
57
|
unknownFlags: Map<string, boolean | string>;
|
|
58
|
+
/**
|
|
59
|
+
* `--`/`-` prefixed tokens this parse could not match against any built-in
|
|
60
|
+
* or {@link extensionFlags} entry. The startup parse runs *before*
|
|
61
|
+
* extensions load, so it always lists every extension-registered flag here;
|
|
62
|
+
* the post-extension reparse in {@link applyExtensionFlags} clears those
|
|
63
|
+
* once the real flag set is known. Anything still present after that
|
|
64
|
+
* reparse is a genuine typo or stale flag and {@link reportUnrecognizedFlags}
|
|
65
|
+
* surfaces it as a hard error so the agent does not silently start a
|
|
66
|
+
* session with the misparsed positionals as a prompt (issue #2459).
|
|
67
|
+
*/
|
|
68
|
+
unrecognizedFlags: string[];
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
|
|
@@ -68,12 +78,23 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
68
78
|
messages: [],
|
|
69
79
|
fileArgs: [],
|
|
70
80
|
unknownFlags: new Map(),
|
|
81
|
+
unrecognizedFlags: [],
|
|
71
82
|
};
|
|
72
83
|
|
|
84
|
+
let sawSeparator = false;
|
|
73
85
|
for (let i = 0; i < args.length; i++) {
|
|
74
86
|
let arg = args[i];
|
|
75
87
|
const flagIndex = i;
|
|
76
88
|
|
|
89
|
+
// POSIX positional separator: once `--` lands, every remaining token is
|
|
90
|
+
// a positional regardless of shape. Without this, a flag-looking message
|
|
91
|
+
// (`omp -p -- --explain-this`) would be re-validated by the loop below
|
|
92
|
+
// and rejected by the unknown-flag guard (#2461 review).
|
|
93
|
+
if (sawSeparator) {
|
|
94
|
+
result.messages.push(arg);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
77
98
|
// Support --flag=value syntax (e.g. --tools=ask,read). The value is
|
|
78
99
|
// spliced in as the next token so value-consuming flags pick it up via
|
|
79
100
|
// `args[++i]`; a non-consuming flag (e.g. a boolean) leaves it behind and
|
|
@@ -228,17 +249,24 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
228
249
|
} else if (arg === "--skills" && i + 1 < args.length) {
|
|
229
250
|
// Comma-separated glob patterns for skill filtering
|
|
230
251
|
result.skills = args[++i].split(",").map(s => s.trim());
|
|
231
|
-
} else if (arg === "--list-models") {
|
|
232
|
-
// Check if next arg is a search pattern (not a flag or file arg)
|
|
233
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@")) {
|
|
234
|
-
result.listModels = args[++i];
|
|
235
|
-
} else {
|
|
236
|
-
result.listModels = true;
|
|
237
|
-
}
|
|
238
252
|
} else if (arg.startsWith("@")) {
|
|
239
253
|
result.fileArgs.push(arg.slice(1)); // Remove @ prefix
|
|
240
|
-
} else if (!arg.startsWith("-")) {
|
|
254
|
+
} else if (!arg.startsWith("-") || arg === "-") {
|
|
255
|
+
// Plain positional or lone `-` (stdin marker) — pass through as a
|
|
256
|
+
// message rather than flagging it.
|
|
241
257
|
result.messages.push(arg);
|
|
258
|
+
} else if (arg === "--") {
|
|
259
|
+
// POSIX positional separator: drop the token and switch the loop
|
|
260
|
+
// into "everything from here is a positional" mode. The guard at
|
|
261
|
+
// the top of the loop body handles the remaining tokens.
|
|
262
|
+
sawSeparator = true;
|
|
263
|
+
} else {
|
|
264
|
+
// Flag-shaped (`-x`, `--name`) but unrecognized at this parse. Record
|
|
265
|
+
// it so the post-extension reparse can decide whether to surface it
|
|
266
|
+
// as a hard error. `--flag=value` already split `value` into the next
|
|
267
|
+
// slot; the standard "drop unconsumed equals value" guard below
|
|
268
|
+
// removes it so it does not leak into messages (issue #2459).
|
|
269
|
+
result.unrecognizedFlags.push(arg);
|
|
242
270
|
}
|
|
243
271
|
// Drop an unconsumed `--flag=value` value (e.g. a boolean flag): when no
|
|
244
272
|
// branch advanced past the spliced token, remove it so it does not fall
|
|
@@ -251,6 +279,24 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
251
279
|
return result;
|
|
252
280
|
}
|
|
253
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Emit a stderr error listing the unrecognized flags and return `true` when
|
|
284
|
+
* there were any. Caller is expected to exit with a non-zero status. Splitting
|
|
285
|
+
* the print from the exit keeps the helper unit-testable without forking a
|
|
286
|
+
* process (issue #2459).
|
|
287
|
+
*/
|
|
288
|
+
export function reportUnrecognizedFlags(
|
|
289
|
+
args: Pick<Args, "unrecognizedFlags">,
|
|
290
|
+
write: (text: string) => void = text => process.stderr.write(text),
|
|
291
|
+
): boolean {
|
|
292
|
+
if (args.unrecognizedFlags.length === 0) return false;
|
|
293
|
+
const flags = args.unrecognizedFlags;
|
|
294
|
+
const plural = flags.length === 1 ? "" : "s";
|
|
295
|
+
write(`${chalk.red(`Error: unknown flag${plural}: ${flags.join(", ")}`)}\n`);
|
|
296
|
+
write(`Run \`${APP_NAME} --help\` for available flags.\n`);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
254
300
|
export function getExtraHelpText(): string {
|
|
255
301
|
return `${chalk.bold("Environment Variables:")}
|
|
256
302
|
${chalk.dim("# Core Providers")}
|
|
@@ -409,7 +409,7 @@ function pickProbeCandidates(provider: string): Model<Api>[] {
|
|
|
409
409
|
if (!model.input.includes("text")) return false;
|
|
410
410
|
const totalCost = (model.cost?.input ?? 0) + (model.cost?.output ?? 0);
|
|
411
411
|
if (!Number.isFinite(totalCost) || totalCost < 0) return false;
|
|
412
|
-
if (model.maxTokens <= 0) return false;
|
|
412
|
+
if (model.maxTokens !== null && model.maxTokens <= 0) return false;
|
|
413
413
|
return true;
|
|
414
414
|
});
|
|
415
415
|
candidates.sort((a, b) => a.cost.input + a.cost.output - (b.cost.input + b.cost.output) || a.id.localeCompare(b.id));
|
package/src/cli/bench-cli.ts
CHANGED
|
@@ -180,7 +180,7 @@ async function runBenchRequest(
|
|
|
180
180
|
apiKey: options.apiKey,
|
|
181
181
|
sessionId: options.sessionId,
|
|
182
182
|
maxTokens:
|
|
183
|
-
Number.isFinite(model.maxTokens) && model.maxTokens > 0
|
|
183
|
+
model.maxTokens !== null && Number.isFinite(model.maxTokens) && model.maxTokens > 0
|
|
184
184
|
? Math.min(options.maxTokens, model.maxTokens)
|
|
185
185
|
: options.maxTokens,
|
|
186
186
|
reasoning: options.reasoning,
|