@oh-my-pi/pi-coding-agent 8.0.20 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/docs/session.md +111 -46
- package/examples/custom-tools/hello/index.ts +1 -1
- package/examples/custom-tools/todo/index.ts +3 -4
- package/examples/extensions/api-demo.ts +0 -1
- package/examples/extensions/chalk-logger.ts +2 -3
- package/examples/extensions/hello.ts +0 -1
- package/examples/extensions/pirate.ts +0 -1
- package/examples/extensions/plan-mode.ts +15 -16
- package/examples/extensions/todo.ts +3 -4
- package/examples/extensions/tools.ts +1 -2
- package/examples/extensions/with-deps/index.ts +0 -1
- package/examples/hooks/auto-commit-on-exit.ts +1 -2
- package/examples/hooks/confirm-destructive.ts +0 -1
- package/examples/hooks/custom-compaction.ts +1 -2
- package/examples/hooks/dirty-repo-guard.ts +0 -1
- package/examples/hooks/file-trigger.ts +3 -4
- package/examples/hooks/git-checkpoint.ts +0 -1
- package/examples/hooks/handoff.ts +3 -4
- package/examples/hooks/permission-gate.ts +1 -2
- package/examples/hooks/protected-paths.ts +1 -2
- package/examples/hooks/qna.ts +2 -3
- package/examples/hooks/snake.ts +4 -5
- package/examples/hooks/status-line.ts +0 -1
- package/examples/sdk/01-minimal.ts +2 -3
- package/examples/sdk/02-custom-model.ts +2 -3
- package/examples/sdk/03-custom-prompt.ts +3 -4
- package/examples/sdk/04-skills.ts +2 -3
- package/examples/sdk/06-extensions.ts +1 -2
- package/examples/sdk/06-hooks.ts +6 -7
- package/examples/sdk/07-context-files.ts +0 -1
- package/examples/sdk/08-prompt-templates.ts +0 -1
- package/examples/sdk/08-slash-commands.ts +0 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
- package/examples/sdk/10-settings.ts +0 -1
- package/examples/sdk/11-sessions.ts +0 -1
- package/package.json +54 -23
- package/scripts/format-prompts.ts +0 -1
- package/src/capability/context-file.ts +3 -4
- package/src/capability/extension-module.ts +3 -4
- package/src/capability/extension.ts +3 -4
- package/src/capability/fs.ts +20 -21
- package/src/capability/hook.ts +3 -4
- package/src/capability/index.ts +15 -16
- package/src/capability/instruction.ts +3 -4
- package/src/capability/mcp.ts +3 -4
- package/src/capability/prompt.ts +3 -4
- package/src/capability/rule.ts +3 -4
- package/src/capability/settings.ts +2 -3
- package/src/capability/skill.ts +3 -4
- package/src/capability/slash-command.ts +3 -4
- package/src/capability/ssh.ts +3 -4
- package/src/capability/system-prompt.ts +3 -4
- package/src/capability/tool.ts +3 -4
- package/src/cli/args.ts +5 -6
- package/src/cli/config-cli.ts +6 -7
- package/src/cli/file-processor.ts +19 -17
- package/src/cli/jupyter-cli.ts +105 -0
- package/src/cli/list-models.ts +10 -11
- package/src/cli/plugin-cli.ts +20 -25
- package/src/cli/session-picker.ts +2 -3
- package/src/cli/setup-cli.ts +2 -3
- package/src/cli/stats-cli.ts +2 -3
- package/src/cli/update-cli.ts +25 -22
- package/src/commit/agentic/agent.ts +307 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +351 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +26 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +40 -0
- package/src/commit/agentic/state.ts +69 -0
- package/src/commit/agentic/tools/analyze-file.ts +131 -0
- package/src/commit/agentic/tools/git-file-diff.ts +194 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +84 -0
- package/src/commit/agentic/tools/index.ts +56 -0
- package/src/commit/agentic/tools/propose-changelog.ts +128 -0
- package/src/commit/agentic/tools/propose-commit.ts +154 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/split-commit.ts +280 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +200 -0
- package/src/commit/analysis/conventional.ts +165 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +112 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +36 -0
- package/src/commit/changelog/generate.ts +110 -0
- package/src/commit/changelog/index.ts +233 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +93 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/git/errors.ts +11 -0
- package/src/commit/git/index.ts +212 -0
- package/src/commit/git/operations.ts +53 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +63 -0
- package/src/commit/map-reduce/map-phase.ts +178 -0
- package/src/commit/map-reduce/reduce-phase.ts +145 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +80 -0
- package/src/commit/pipeline.ts +240 -0
- package/src/commit/prompts/analysis-system.md +155 -0
- package/src/commit/prompts/analysis-user.md +41 -0
- package/src/commit/prompts/changelog-system.md +56 -0
- package/src/commit/prompts/changelog-user.md +19 -0
- package/src/commit/prompts/file-observer-system.md +26 -0
- package/src/commit/prompts/file-observer-user.md +9 -0
- package/src/commit/prompts/reduce-system.md +60 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +4 -0
- package/src/commit/prompts/summary-system.md +52 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +109 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +6 -8
- package/src/config/model-registry.ts +65 -38
- package/src/config/model-resolver.ts +18 -19
- package/src/config/prompt-templates.ts +11 -11
- package/src/config/settings-manager.ts +141 -50
- package/src/config.ts +64 -66
- package/src/cursor.ts +11 -9
- package/src/discovery/agents-md.ts +11 -12
- package/src/discovery/builtin.ts +68 -73
- package/src/discovery/claude.ts +41 -42
- package/src/discovery/cline.ts +11 -12
- package/src/discovery/codex.ts +52 -53
- package/src/discovery/cursor.ts +9 -10
- package/src/discovery/gemini.ts +17 -22
- package/src/discovery/github.ts +13 -14
- package/src/discovery/helpers.ts +35 -34
- package/src/discovery/index.ts +22 -24
- package/src/discovery/mcp-json.ts +8 -9
- package/src/discovery/ssh.ts +8 -9
- package/src/discovery/vscode.ts +4 -5
- package/src/discovery/windsurf.ts +6 -7
- package/src/exa/company.ts +1 -2
- package/src/exa/index.ts +2 -3
- package/src/exa/linkedin.ts +1 -2
- package/src/exa/mcp-client.ts +14 -16
- package/src/exa/render.ts +10 -11
- package/src/exa/researcher.ts +1 -2
- package/src/exa/search.ts +1 -2
- package/src/exa/types.ts +0 -1
- package/src/exa/websets.ts +1 -2
- package/src/exec/bash-executor.ts +3 -4
- package/src/exec/exec.ts +0 -1
- package/src/export/custom-share.ts +5 -6
- package/src/export/html/index.ts +24 -21
- package/src/export/ttsr.ts +2 -3
- package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
- package/src/extensibility/custom-commands/loader.ts +18 -15
- package/src/extensibility/custom-commands/types.ts +2 -3
- package/src/extensibility/custom-tools/loader.ts +11 -12
- package/src/extensibility/custom-tools/types.ts +7 -8
- package/src/extensibility/custom-tools/wrapper.ts +2 -3
- package/src/extensibility/extensions/loader.ts +76 -54
- package/src/extensibility/extensions/runner.ts +11 -12
- package/src/extensibility/extensions/types.ts +20 -27
- package/src/extensibility/extensions/wrapper.ts +3 -4
- package/src/extensibility/hooks/index.ts +1 -1
- package/src/extensibility/hooks/loader.ts +9 -10
- package/src/extensibility/hooks/runner.ts +7 -8
- package/src/extensibility/hooks/tool-wrapper.ts +0 -1
- package/src/extensibility/hooks/types.ts +11 -18
- package/src/extensibility/plugins/doctor.ts +3 -3
- package/src/extensibility/plugins/installer.ts +27 -27
- package/src/extensibility/plugins/loader.ts +59 -56
- package/src/extensibility/plugins/manager.ts +211 -171
- package/src/extensibility/plugins/parser.ts +1 -1
- package/src/extensibility/plugins/paths.ts +8 -8
- package/src/extensibility/skills.ts +63 -60
- package/src/extensibility/slash-commands.ts +10 -10
- package/src/index.ts +54 -54
- package/src/internal-urls/agent-protocol.ts +21 -11
- package/src/internal-urls/artifact-protocol.ts +17 -13
- package/src/internal-urls/router.ts +1 -2
- package/src/internal-urls/rule-protocol.ts +3 -4
- package/src/internal-urls/skill-protocol.ts +3 -4
- package/src/ipy/executor.ts +109 -9
- package/src/ipy/gateway-coordinator.ts +79 -90
- package/src/ipy/kernel.ts +32 -30
- package/src/ipy/modules.ts +13 -13
- package/src/lsp/client.ts +21 -10
- package/src/lsp/clients/biome-client.ts +1 -2
- package/src/lsp/clients/index.ts +3 -3
- package/src/lsp/clients/lsp-linter-client.ts +4 -5
- package/src/lsp/config.ts +15 -15
- package/src/lsp/edits.ts +4 -5
- package/src/lsp/index.ts +43 -44
- package/src/lsp/lspmux.ts +8 -8
- package/src/lsp/render.ts +99 -61
- package/src/lsp/utils.ts +3 -3
- package/src/main.ts +71 -37
- package/src/mcp/client.ts +2 -3
- package/src/mcp/config.ts +5 -6
- package/src/mcp/json-rpc.ts +0 -1
- package/src/mcp/loader.ts +6 -7
- package/src/mcp/manager.ts +17 -18
- package/src/mcp/tool-bridge.ts +4 -9
- package/src/mcp/tool-cache.ts +2 -3
- package/src/mcp/transports/http.ts +2 -4
- package/src/mcp/transports/stdio.ts +1 -2
- package/src/migrations.ts +63 -52
- package/src/modes/components/armin.ts +4 -5
- package/src/modes/components/assistant-message.ts +33 -5
- package/src/modes/components/bash-execution.ts +7 -8
- package/src/modes/components/bordered-loader.ts +3 -3
- package/src/modes/components/branch-summary-message.ts +3 -3
- package/src/modes/components/compaction-summary-message.ts +3 -3
- package/src/modes/components/countdown-timer.ts +0 -1
- package/src/modes/components/custom-message.ts +5 -5
- package/src/modes/components/diff.ts +1 -1
- package/src/modes/components/dynamic-border.ts +2 -2
- package/src/modes/components/extensions/extension-dashboard.ts +6 -7
- package/src/modes/components/extensions/extension-list.ts +2 -3
- package/src/modes/components/extensions/inspector-panel.ts +3 -4
- package/src/modes/components/extensions/state-manager.ts +25 -26
- package/src/modes/components/extensions/types.ts +1 -2
- package/src/modes/components/footer.ts +47 -43
- package/src/modes/components/history-search.ts +2 -2
- package/src/modes/components/hook-editor.ts +3 -4
- package/src/modes/components/hook-input.ts +2 -3
- package/src/modes/components/hook-message.ts +5 -5
- package/src/modes/components/hook-selector.ts +2 -3
- package/src/modes/components/keybinding-hints.ts +2 -3
- package/src/modes/components/login-dialog.ts +2 -2
- package/src/modes/components/model-selector.ts +12 -12
- package/src/modes/components/oauth-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +20 -20
- package/src/modes/components/python-execution.ts +7 -8
- package/src/modes/components/queue-mode-selector.ts +3 -3
- package/src/modes/components/read-tool-group.ts +2 -2
- package/src/modes/components/session-selector.ts +4 -4
- package/src/modes/components/settings-defs.ts +77 -69
- package/src/modes/components/settings-selector.ts +16 -16
- package/src/modes/components/show-images-selector.ts +2 -2
- package/src/modes/components/status-line/segments.ts +4 -4
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line/types.ts +2 -2
- package/src/modes/components/status-line-segment-editor.ts +7 -8
- package/src/modes/components/status-line.ts +12 -12
- package/src/modes/components/theme-selector.ts +8 -7
- package/src/modes/components/thinking-selector.ts +4 -4
- package/src/modes/components/todo-display.ts +2 -2
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +16 -19
- package/src/modes/components/tree-selector.ts +12 -12
- package/src/modes/components/ttsr-notification.ts +5 -5
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +1 -1
- package/src/modes/components/visual-truncate.ts +0 -1
- package/src/modes/components/welcome.ts +4 -4
- package/src/modes/controllers/command-controller.ts +46 -47
- package/src/modes/controllers/event-controller.ts +16 -20
- package/src/modes/controllers/extension-ui-controller.ts +40 -46
- package/src/modes/controllers/input-controller.ts +17 -18
- package/src/modes/controllers/selector-controller.ts +103 -91
- package/src/modes/index.ts +3 -3
- package/src/modes/interactive-mode.ts +31 -31
- package/src/modes/print-mode.ts +12 -13
- package/src/modes/rpc/rpc-client.ts +7 -8
- package/src/modes/rpc/rpc-mode.ts +24 -28
- package/src/modes/rpc/rpc-types.ts +3 -4
- package/src/modes/theme/mermaid-cache.ts +89 -0
- package/src/modes/theme/theme.ts +130 -53
- package/src/modes/types.ts +10 -10
- package/src/modes/utils/ui-helpers.ts +17 -17
- package/src/patch/applicator.ts +18 -19
- package/src/patch/diff.ts +1 -2
- package/src/patch/fuzzy.ts +1 -2
- package/src/patch/index.ts +11 -18
- package/src/patch/normalize.ts +4 -4
- package/src/patch/normative.ts +1 -2
- package/src/patch/parser.ts +8 -9
- package/src/patch/shared.ts +43 -16
- package/src/prompts/tools/task.md +2 -0
- package/src/sdk.ts +100 -65
- package/src/session/agent-session.ts +84 -85
- package/src/session/agent-storage.ts +43 -39
- package/src/session/artifacts.ts +32 -10
- package/src/session/auth-storage.ts +50 -39
- package/src/session/compaction/branch-summarization.ts +7 -10
- package/src/session/compaction/compaction.ts +8 -19
- package/src/session/compaction/utils.ts +6 -9
- package/src/session/history-storage.ts +10 -10
- package/src/session/messages.ts +4 -5
- package/src/session/session-manager.ts +76 -65
- package/src/session/session-storage.ts +57 -69
- package/src/session/storage-migration.ts +14 -56
- package/src/session/streaming-output.ts +2 -2
- package/src/ssh/connection-manager.ts +43 -50
- package/src/ssh/ssh-executor.ts +2 -2
- package/src/ssh/sshfs-mount.ts +11 -18
- package/src/system-prompt.ts +28 -35
- package/src/task/agents.ts +45 -30
- package/src/task/commands.ts +6 -7
- package/src/task/discovery.ts +39 -76
- package/src/task/executor.ts +14 -15
- package/src/task/index.ts +40 -34
- package/src/task/output-manager.ts +93 -0
- package/src/task/parallel.ts +0 -1
- package/src/task/render.ts +24 -30
- package/src/task/subprocess-tool-registry.ts +1 -2
- package/src/task/worker-protocol.ts +3 -3
- package/src/task/worker.ts +33 -39
- package/src/task/worktree.ts +19 -19
- package/src/tools/ask.ts +41 -20
- package/src/tools/bash-interceptor.ts +1 -5
- package/src/tools/bash.ts +91 -97
- package/src/tools/calculator.ts +49 -47
- package/src/tools/complete.ts +4 -5
- package/src/tools/context.ts +2 -2
- package/src/tools/fetch.ts +84 -124
- package/src/tools/find.ts +94 -98
- package/src/tools/gemini-image.ts +14 -14
- package/src/tools/grep.ts +100 -116
- package/src/tools/index.ts +80 -55
- package/src/tools/list-limit.ts +1 -1
- package/src/tools/ls.ts +44 -70
- package/src/tools/notebook.ts +51 -67
- package/src/tools/output-meta.ts +3 -4
- package/src/tools/output-utils.ts +2 -2
- package/src/tools/path-utils.ts +5 -5
- package/src/tools/python.ts +104 -217
- package/src/tools/read.ts +92 -33
- package/src/tools/render-utils.ts +8 -23
- package/src/tools/renderers.ts +6 -7
- package/src/tools/review.ts +8 -11
- package/src/tools/ssh.ts +69 -49
- package/src/tools/todo-write.ts +37 -25
- package/src/tools/tool-errors.ts +3 -3
- package/src/tools/tool-result.ts +3 -8
- package/src/tools/write.ts +99 -75
- package/src/tui/code-cell.ts +109 -0
- package/src/tui/file-list.ts +47 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +72 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +55 -0
- package/src/tui/types.ts +16 -0
- package/src/tui/utils.ts +48 -0
- package/src/utils/changelog.ts +9 -10
- package/src/utils/clipboard.ts +11 -11
- package/src/utils/file-mentions.ts +4 -10
- package/src/utils/frontmatter.ts +6 -3
- package/src/utils/fuzzy.ts +2 -2
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/mime.ts +2 -2
- package/src/utils/shell-snapshot.ts +11 -13
- package/src/utils/shell.ts +4 -5
- package/src/utils/title-generator.ts +8 -9
- package/src/utils/tools-manager.ts +23 -23
- package/src/vendor/photon/index.js +1099 -1059
- package/src/vendor/photon/photon_rs_bg.wasm +0 -0
- package/src/web/scrapers/artifacthub.ts +1 -1
- package/src/web/scrapers/arxiv.ts +2 -2
- package/src/web/scrapers/bluesky.ts +2 -2
- package/src/web/scrapers/cheatsh.ts +1 -1
- package/src/web/scrapers/chocolatey.ts +2 -2
- package/src/web/scrapers/choosealicense.ts +5 -5
- package/src/web/scrapers/cisa-kev.ts +1 -1
- package/src/web/scrapers/crossref.ts +2 -2
- package/src/web/scrapers/devto.ts +3 -3
- package/src/web/scrapers/discogs.ts +3 -4
- package/src/web/scrapers/discourse.ts +1 -1
- package/src/web/scrapers/dockerhub.ts +1 -1
- package/src/web/scrapers/fdroid.ts +2 -2
- package/src/web/scrapers/firefox-addons.ts +3 -3
- package/src/web/scrapers/flathub.ts +1 -1
- package/src/web/scrapers/github.ts +3 -3
- package/src/web/scrapers/gitlab.ts +4 -4
- package/src/web/scrapers/hackernews.ts +2 -2
- package/src/web/scrapers/huggingface.ts +1 -1
- package/src/web/scrapers/iacr.ts +2 -2
- package/src/web/scrapers/index.ts +0 -1
- package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
- package/src/web/scrapers/lemmy.ts +2 -2
- package/src/web/scrapers/maven.ts +2 -2
- package/src/web/scrapers/mdn.ts +2 -4
- package/src/web/scrapers/metacpan.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/npm.ts +1 -1
- package/src/web/scrapers/nuget.ts +2 -2
- package/src/web/scrapers/nvd.ts +3 -3
- package/src/web/scrapers/ollama.ts +7 -9
- package/src/web/scrapers/opencorporates.ts +2 -2
- package/src/web/scrapers/openlibrary.ts +6 -6
- package/src/web/scrapers/orcid.ts +0 -1
- package/src/web/scrapers/osv.ts +2 -2
- package/src/web/scrapers/packagist.ts +1 -1
- package/src/web/scrapers/pubmed.ts +1 -2
- package/src/web/scrapers/rawg.ts +2 -2
- package/src/web/scrapers/readthedocs.ts +1 -2
- package/src/web/scrapers/repology.ts +2 -2
- package/src/web/scrapers/rfc.ts +1 -1
- package/src/web/scrapers/searchcode.ts +2 -2
- package/src/web/scrapers/semantic-scholar.ts +1 -1
- package/src/web/scrapers/snapcraft.ts +2 -2
- package/src/web/scrapers/sourcegraph.ts +1 -1
- package/src/web/scrapers/spdx.ts +3 -3
- package/src/web/scrapers/spotify.ts +0 -1
- package/src/web/scrapers/twitter.ts +1 -1
- package/src/web/scrapers/types.ts +1 -2
- package/src/web/scrapers/utils.ts +5 -5
- package/src/web/scrapers/wikidata.ts +3 -3
- package/src/web/scrapers/youtube.ts +9 -14
- package/src/web/search/auth.ts +5 -10
- package/src/web/search/index.ts +11 -21
- package/src/web/search/providers/anthropic.ts +3 -9
- package/src/web/search/providers/exa.ts +6 -10
- package/src/web/search/providers/perplexity.ts +5 -5
- package/src/web/search/render.ts +129 -175
- package/tsconfig.json +0 -42
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Modes use this class and add their own I/O layer on top.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
17
|
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
18
18
|
import type {
|
|
19
19
|
AssistantMessage,
|
|
@@ -26,21 +26,23 @@ import type {
|
|
|
26
26
|
UsageReport,
|
|
27
27
|
} from "@oh-my-pi/pi-ai";
|
|
28
28
|
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
29
|
-
import
|
|
30
|
-
import {
|
|
31
|
-
import type {
|
|
32
|
-
import {
|
|
29
|
+
import { abortableSleep, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
30
|
+
import { YAML } from "bun";
|
|
31
|
+
import type { Rule } from "../capability/rule";
|
|
32
|
+
import { getAgentDbPath } from "../config";
|
|
33
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
34
|
+
import { parseModelString } from "../config/model-resolver";
|
|
33
35
|
import {
|
|
34
36
|
expandPromptTemplate,
|
|
35
37
|
type PromptTemplate,
|
|
36
38
|
parseCommandArgs,
|
|
37
39
|
renderPromptTemplate,
|
|
38
|
-
} from "
|
|
39
|
-
import type { SettingsManager, SkillsSettings } from "
|
|
40
|
-
import { type BashResult, executeBash as executeBashCommand } from "
|
|
41
|
-
import { exportSessionToHtml } from "
|
|
42
|
-
import type { TtsrManager } from "
|
|
43
|
-
import type { LoadedCustomCommand } from "
|
|
40
|
+
} from "../config/prompt-templates";
|
|
41
|
+
import type { SettingsManager, SkillsSettings } from "../config/settings-manager";
|
|
42
|
+
import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
|
|
43
|
+
import { exportSessionToHtml } from "../export/html";
|
|
44
|
+
import type { TtsrManager } from "../export/ttsr";
|
|
45
|
+
import type { LoadedCustomCommand } from "../extensibility/custom-commands";
|
|
44
46
|
import type {
|
|
45
47
|
ExtensionCommandContext,
|
|
46
48
|
ExtensionRunner,
|
|
@@ -52,23 +54,21 @@ import type {
|
|
|
52
54
|
TreePreparation,
|
|
53
55
|
TurnEndEvent,
|
|
54
56
|
TurnStartEvent,
|
|
55
|
-
} from "
|
|
56
|
-
import type { CompactOptions, ContextUsage } from "
|
|
57
|
-
import type { HookCommandContext } from "
|
|
58
|
-
import type { Skill, SkillWarning } from "
|
|
59
|
-
import { expandSlashCommand, type FileSlashCommand } from "
|
|
60
|
-
import { executePython as executePythonCommand, type PythonResult } from "
|
|
61
|
-
import { theme } from "
|
|
62
|
-
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "
|
|
63
|
-
import ttsrInterruptTemplate from "
|
|
64
|
-
import { closeAllConnections } from "
|
|
65
|
-
import { unmountAll } from "
|
|
66
|
-
import { outputMeta } from "
|
|
67
|
-
import { resolveToCwd } from "
|
|
68
|
-
import type { TodoItem } from "
|
|
69
|
-
import { extractFileMentions, generateFileMentionMessages } from "
|
|
70
|
-
import { abortableSleep, logger } from "@oh-my-pi/pi-utils";
|
|
71
|
-
import { YAML } from "bun";
|
|
57
|
+
} from "../extensibility/extensions";
|
|
58
|
+
import type { CompactOptions, ContextUsage } from "../extensibility/extensions/types";
|
|
59
|
+
import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
60
|
+
import type { Skill, SkillWarning } from "../extensibility/skills";
|
|
61
|
+
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
62
|
+
import { executePython as executePythonCommand, type PythonResult } from "../ipy/executor";
|
|
63
|
+
import { theme } from "../modes/theme/theme";
|
|
64
|
+
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../patch";
|
|
65
|
+
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
66
|
+
import { closeAllConnections } from "../ssh/connection-manager";
|
|
67
|
+
import { unmountAll } from "../ssh/sshfs-mount";
|
|
68
|
+
import { outputMeta } from "../tools/output-meta";
|
|
69
|
+
import { resolveToCwd } from "../tools/path-utils";
|
|
70
|
+
import type { TodoItem } from "../tools/todo-write";
|
|
71
|
+
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
72
72
|
import {
|
|
73
73
|
type CompactionResult,
|
|
74
74
|
calculateContextTokens,
|
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
generateBranchSummary,
|
|
79
79
|
prepareCompaction,
|
|
80
80
|
shouldCompact,
|
|
81
|
-
} from "./compaction
|
|
81
|
+
} from "./compaction";
|
|
82
82
|
import {
|
|
83
83
|
type BashExecutionMessage,
|
|
84
84
|
type BranchSummaryMessage,
|
|
@@ -214,9 +214,9 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
214
214
|
get theme() {
|
|
215
215
|
return theme;
|
|
216
216
|
},
|
|
217
|
-
getAllThemes: () => [],
|
|
218
|
-
getTheme: () => undefined,
|
|
219
|
-
setTheme:
|
|
217
|
+
getAllThemes: () => Promise.resolve([]),
|
|
218
|
+
getTheme: () => Promise.resolve(undefined),
|
|
219
|
+
setTheme: _theme => Promise.resolve({ success: false, error: "UI not available" }),
|
|
220
220
|
setFooter: () => {},
|
|
221
221
|
setHeader: () => {},
|
|
222
222
|
setEditorComponent: () => {},
|
|
@@ -542,7 +542,7 @@ export class AgentSession {
|
|
|
542
542
|
private _getTtsrInjectionContent(): string | undefined {
|
|
543
543
|
if (this._pendingTtsrInjections.length === 0) return undefined;
|
|
544
544
|
const content = this._pendingTtsrInjections
|
|
545
|
-
.map(
|
|
545
|
+
.map(r => renderPromptTemplate(ttsrInterruptTemplate, { name: r.name, path: r.path, content: r.content }))
|
|
546
546
|
.join("\n\n");
|
|
547
547
|
this._pendingTtsrInjections = [];
|
|
548
548
|
return content;
|
|
@@ -553,10 +553,10 @@ export class AgentSession {
|
|
|
553
553
|
if (message.role !== "user") return "";
|
|
554
554
|
const content = message.content;
|
|
555
555
|
if (typeof content === "string") return content;
|
|
556
|
-
const textBlocks = content.filter(
|
|
557
|
-
const text = textBlocks.map(
|
|
556
|
+
const textBlocks = content.filter(c => c.type === "text");
|
|
557
|
+
const text = textBlocks.map(c => (c as TextContent).text).join("");
|
|
558
558
|
if (text.length > 0) return text;
|
|
559
|
-
const hasImages = content.some(
|
|
559
|
+
const hasImages = content.some(c => c.type === "image");
|
|
560
560
|
return hasImages ? "[Image]" : "";
|
|
561
561
|
}
|
|
562
562
|
|
|
@@ -578,7 +578,7 @@ export class AgentSession {
|
|
|
578
578
|
this._streamingEditFileCache.clear();
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
-
private _preCacheStreamingEditFile(event: AgentEvent): void {
|
|
581
|
+
private async _preCacheStreamingEditFile(event: AgentEvent): Promise<void> {
|
|
582
582
|
if (!this.settingsManager.getEditStreamingAbort()) return;
|
|
583
583
|
if (event.type !== "message_update") return;
|
|
584
584
|
const assistantEvent = event.assistantMessageEvent;
|
|
@@ -606,13 +606,11 @@ export class AgentSession {
|
|
|
606
606
|
if (this._streamingEditFileCache.has(resolvedPath)) return;
|
|
607
607
|
|
|
608
608
|
try {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
this._streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
|
|
613
|
-
}
|
|
609
|
+
const rawText = fs.readFileSync(resolvedPath, "utf-8");
|
|
610
|
+
const { text } = stripBom(rawText);
|
|
611
|
+
this._streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
|
|
614
612
|
} catch {
|
|
615
|
-
// Don't cache on read errors - let the edit tool handle them
|
|
613
|
+
// Don't cache on read errors (including ENOENT) - let the edit tool handle them
|
|
616
614
|
}
|
|
617
615
|
}
|
|
618
616
|
|
|
@@ -655,7 +653,7 @@ export class AgentSession {
|
|
|
655
653
|
const normalizedDiff = normalizeDiff(diffForCheck.replace(/\r/g, ""));
|
|
656
654
|
if (!normalizedDiff) return;
|
|
657
655
|
const lines = normalizedDiff.split("\n");
|
|
658
|
-
const hasChangeLine = lines.some(
|
|
656
|
+
const hasChangeLine = lines.some(line => line.startsWith("+") || line.startsWith("-"));
|
|
659
657
|
if (!hasChangeLine) return;
|
|
660
658
|
|
|
661
659
|
const lineCount = lines.length;
|
|
@@ -666,13 +664,13 @@ export class AgentSession {
|
|
|
666
664
|
const rename = typeof args.rename === "string" ? args.rename : undefined;
|
|
667
665
|
|
|
668
666
|
const removedLines = lines
|
|
669
|
-
.filter(
|
|
670
|
-
.map(
|
|
667
|
+
.filter(line => line.startsWith("-") && !line.startsWith("--- "))
|
|
668
|
+
.map(line => line.slice(1));
|
|
671
669
|
if (removedLines.length > 0) {
|
|
672
670
|
const resolvedPath = resolveToCwd(path, this.sessionManager.getCwd());
|
|
673
671
|
const cachedContent = this._streamingEditFileCache.get(resolvedPath);
|
|
674
672
|
if (cachedContent !== undefined) {
|
|
675
|
-
const missing = removedLines.find(
|
|
673
|
+
const missing = removedLines.find(line => !cachedContent.includes(normalizeToLF(line)));
|
|
676
674
|
if (missing) {
|
|
677
675
|
this._streamingEditAbortTriggered = true;
|
|
678
676
|
logger.warn("Streaming edit aborted due to patch preview failure", {
|
|
@@ -701,10 +699,9 @@ export class AgentSession {
|
|
|
701
699
|
): Promise<void> {
|
|
702
700
|
if (this._streamingEditAbortTriggered) return;
|
|
703
701
|
try {
|
|
704
|
-
if (!(await Bun.file(resolvedPath).exists())) return;
|
|
705
702
|
const { text } = stripBom(await Bun.file(resolvedPath).text());
|
|
706
703
|
const normalizedContent = normalizeToLF(text);
|
|
707
|
-
const missing = removedLines.find(
|
|
704
|
+
const missing = removedLines.find(line => !normalizedContent.includes(normalizeToLF(line)));
|
|
708
705
|
if (missing) {
|
|
709
706
|
this._streamingEditAbortTriggered = true;
|
|
710
707
|
logger.warn("Streaming edit aborted due to patch preview failure", {
|
|
@@ -714,8 +711,12 @@ export class AgentSession {
|
|
|
714
711
|
});
|
|
715
712
|
this.agent.abort();
|
|
716
713
|
}
|
|
717
|
-
} catch {
|
|
718
|
-
// Ignore
|
|
714
|
+
} catch (err) {
|
|
715
|
+
// Ignore ENOENT (file not found) - let the edit tool handle missing files
|
|
716
|
+
// Also ignore other errors during async fallback
|
|
717
|
+
if (!isEnoent(err)) {
|
|
718
|
+
// Log unexpected errors but don't abort
|
|
719
|
+
}
|
|
719
720
|
}
|
|
720
721
|
}
|
|
721
722
|
|
|
@@ -885,7 +886,7 @@ export class AgentSession {
|
|
|
885
886
|
* Returns the names of tools currently set on the agent.
|
|
886
887
|
*/
|
|
887
888
|
getActiveToolNames(): string[] {
|
|
888
|
-
return this.agent.state.tools.map(
|
|
889
|
+
return this.agent.state.tools.map(t => t.name);
|
|
889
890
|
}
|
|
890
891
|
|
|
891
892
|
/**
|
|
@@ -1178,7 +1179,7 @@ export class AgentSession {
|
|
|
1178
1179
|
hasQueuedMessages: () => this.queuedMessageCount > 0,
|
|
1179
1180
|
getContextUsage: () => this.getContextUsage(),
|
|
1180
1181
|
waitForIdle: () => this.agent.waitForIdle(),
|
|
1181
|
-
newSession: async
|
|
1182
|
+
newSession: async options => {
|
|
1182
1183
|
const success = await this.newSession({ parentSession: options?.parentSession });
|
|
1183
1184
|
if (!success) {
|
|
1184
1185
|
return { cancelled: true };
|
|
@@ -1188,7 +1189,7 @@ export class AgentSession {
|
|
|
1188
1189
|
}
|
|
1189
1190
|
return { cancelled: false };
|
|
1190
1191
|
},
|
|
1191
|
-
branch: async
|
|
1192
|
+
branch: async entryId => {
|
|
1192
1193
|
const result = await this.branch(entryId);
|
|
1193
1194
|
return { cancelled: result.cancelled };
|
|
1194
1195
|
},
|
|
@@ -1196,7 +1197,7 @@ export class AgentSession {
|
|
|
1196
1197
|
const result = await this.navigateTree(targetId, { summarize: options?.summarize });
|
|
1197
1198
|
return { cancelled: result.cancelled };
|
|
1198
1199
|
},
|
|
1199
|
-
compact: async
|
|
1200
|
+
compact: async instructionsOrOptions => {
|
|
1200
1201
|
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
1201
1202
|
const options =
|
|
1202
1203
|
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
@@ -1218,7 +1219,7 @@ export class AgentSession {
|
|
|
1218
1219
|
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
|
|
1219
1220
|
|
|
1220
1221
|
// Find matching command
|
|
1221
|
-
const loaded = this._customCommands.find(
|
|
1222
|
+
const loaded = this._customCommands.find(c => c.command.name === commandName);
|
|
1222
1223
|
if (!loaded) return null;
|
|
1223
1224
|
|
|
1224
1225
|
// Get command context from extension runner (includes session control methods)
|
|
@@ -1605,10 +1606,10 @@ export class AgentSession {
|
|
|
1605
1606
|
const parsed = parseModelString(roleModelStr);
|
|
1606
1607
|
let match: Model<any> | undefined;
|
|
1607
1608
|
if (parsed) {
|
|
1608
|
-
match = availableModels.find(
|
|
1609
|
+
match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
1609
1610
|
}
|
|
1610
1611
|
if (!match) {
|
|
1611
|
-
match = availableModels.find(
|
|
1612
|
+
match = availableModels.find(m => m.id.toLowerCase() === roleModelStr.toLowerCase());
|
|
1612
1613
|
}
|
|
1613
1614
|
if (!match) continue;
|
|
1614
1615
|
|
|
@@ -1619,8 +1620,8 @@ export class AgentSession {
|
|
|
1619
1620
|
|
|
1620
1621
|
const lastRole = this.sessionManager.getLastModelChangeRole();
|
|
1621
1622
|
let currentIndex = lastRole
|
|
1622
|
-
? roleModels.findIndex(
|
|
1623
|
-
: roleModels.findIndex(
|
|
1623
|
+
? roleModels.findIndex(entry => entry.role === lastRole)
|
|
1624
|
+
: roleModels.findIndex(entry => modelsAreEqual(entry.model, currentModel));
|
|
1624
1625
|
if (currentIndex === -1) currentIndex = 0;
|
|
1625
1626
|
|
|
1626
1627
|
const nextIndex = (currentIndex + 1) % roleModels.length;
|
|
@@ -1639,7 +1640,7 @@ export class AgentSession {
|
|
|
1639
1640
|
if (this._scopedModels.length <= 1) return undefined;
|
|
1640
1641
|
|
|
1641
1642
|
const currentModel = this.model;
|
|
1642
|
-
let currentIndex = this._scopedModels.findIndex(
|
|
1643
|
+
let currentIndex = this._scopedModels.findIndex(sm => modelsAreEqual(sm.model, currentModel));
|
|
1643
1644
|
|
|
1644
1645
|
if (currentIndex === -1) currentIndex = 0;
|
|
1645
1646
|
const len = this._scopedModels.length;
|
|
@@ -1668,7 +1669,7 @@ export class AgentSession {
|
|
|
1668
1669
|
if (availableModels.length <= 1) return undefined;
|
|
1669
1670
|
|
|
1670
1671
|
const currentModel = this.model;
|
|
1671
|
-
let currentIndex = availableModels.findIndex(
|
|
1672
|
+
let currentIndex = availableModels.findIndex(m => modelsAreEqual(m, currentModel));
|
|
1672
1673
|
|
|
1673
1674
|
if (currentIndex === -1) currentIndex = 0;
|
|
1674
1675
|
const len = availableModels.length;
|
|
@@ -1898,7 +1899,7 @@ export class AgentSession {
|
|
|
1898
1899
|
this.agent.replaceMessages(sessionContext.messages);
|
|
1899
1900
|
|
|
1900
1901
|
// Get the saved compaction entry for the hook
|
|
1901
|
-
const savedCompactionEntry = newEntries.find(
|
|
1902
|
+
const savedCompactionEntry = newEntries.find(e => e.type === "compaction" && e.summary === summary) as
|
|
1902
1903
|
| CompactionEntry
|
|
1903
1904
|
| undefined;
|
|
1904
1905
|
|
|
@@ -1975,7 +1976,7 @@ export class AgentSession {
|
|
|
1975
1976
|
// The error shouldn't trigger another compaction since we already compacted.
|
|
1976
1977
|
// Example: opus fails → switch to codex → compact → switch back to opus → opus error
|
|
1977
1978
|
// is still in context but shouldn't trigger compaction again.
|
|
1978
|
-
const compactionEntry = this.sessionManager.getBranch().find(
|
|
1979
|
+
const compactionEntry = this.sessionManager.getBranch().find(e => e.type === "compaction");
|
|
1979
1980
|
const errorIsFromBeforeCompaction =
|
|
1980
1981
|
compactionEntry && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
|
|
1981
1982
|
|
|
@@ -2022,22 +2023,20 @@ export class AgentSession {
|
|
|
2022
2023
|
if (!sessionFile) return;
|
|
2023
2024
|
|
|
2024
2025
|
const todoPath = `${sessionFile.slice(0, -6)}/todos.json`;
|
|
2025
|
-
const file = Bun.file(todoPath);
|
|
2026
|
-
if (!(await file.exists())) {
|
|
2027
|
-
this._todoReminderCount = 0;
|
|
2028
|
-
return;
|
|
2029
|
-
}
|
|
2030
2026
|
|
|
2031
2027
|
let todos: TodoItem[];
|
|
2032
2028
|
try {
|
|
2033
|
-
const data = await file.json();
|
|
2029
|
+
const data = await Bun.file(todoPath).json();
|
|
2034
2030
|
todos = data?.todos ?? [];
|
|
2035
|
-
} catch {
|
|
2031
|
+
} catch (err) {
|
|
2032
|
+
if (isEnoent(err)) {
|
|
2033
|
+
this._todoReminderCount = 0;
|
|
2034
|
+
}
|
|
2036
2035
|
return;
|
|
2037
2036
|
}
|
|
2038
2037
|
|
|
2039
2038
|
// Check for incomplete todos
|
|
2040
|
-
const incomplete = todos.filter(
|
|
2039
|
+
const incomplete = todos.filter(t => t.status !== "completed");
|
|
2041
2040
|
if (incomplete.length === 0) {
|
|
2042
2041
|
this._todoReminderCount = 0;
|
|
2043
2042
|
return;
|
|
@@ -2045,7 +2044,7 @@ export class AgentSession {
|
|
|
2045
2044
|
|
|
2046
2045
|
// Build reminder message
|
|
2047
2046
|
this._todoReminderCount++;
|
|
2048
|
-
const todoList = incomplete.map(
|
|
2047
|
+
const todoList = incomplete.map(t => `- ${t.content}`).join("\n");
|
|
2049
2048
|
const reminder =
|
|
2050
2049
|
`<system_reminder>\n` +
|
|
2051
2050
|
`You stopped with ${incomplete.length} incomplete todo item(s):\n${todoList}\n\n` +
|
|
@@ -2094,10 +2093,10 @@ export class AgentSession {
|
|
|
2094
2093
|
|
|
2095
2094
|
const parsed = parseModelString(roleModelStr);
|
|
2096
2095
|
if (parsed) {
|
|
2097
|
-
return availableModels.find(
|
|
2096
|
+
return availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
2098
2097
|
}
|
|
2099
2098
|
const roleLower = roleModelStr.toLowerCase();
|
|
2100
|
-
return availableModels.find(
|
|
2099
|
+
return availableModels.find(m => m.id.toLowerCase() === roleLower);
|
|
2101
2100
|
}
|
|
2102
2101
|
|
|
2103
2102
|
private _getCompactionModelCandidates(availableModels: Model<any>[]): Model<any>[] {
|
|
@@ -2295,7 +2294,7 @@ export class AgentSession {
|
|
|
2295
2294
|
this.agent.replaceMessages(sessionContext.messages);
|
|
2296
2295
|
|
|
2297
2296
|
// Get the saved compaction entry for the hook
|
|
2298
|
-
const savedCompactionEntry = newEntries.find(
|
|
2297
|
+
const savedCompactionEntry = newEntries.find(e => e.type === "compaction" && e.summary === summary) as
|
|
2299
2298
|
| CompactionEntry
|
|
2300
2299
|
| undefined;
|
|
2301
2300
|
|
|
@@ -2822,7 +2821,7 @@ export class AgentSession {
|
|
|
2822
2821
|
const provider = defaultModelStr.slice(0, slashIdx);
|
|
2823
2822
|
const modelId = defaultModelStr.slice(slashIdx + 1);
|
|
2824
2823
|
const availableModels = this._modelRegistry.getAvailable();
|
|
2825
|
-
const match = availableModels.find(
|
|
2824
|
+
const match = availableModels.find(m => m.provider === provider && m.id === modelId);
|
|
2826
2825
|
if (match) {
|
|
2827
2826
|
this.agent.setModel(match);
|
|
2828
2827
|
}
|
|
@@ -3026,7 +3025,7 @@ export class AgentSession {
|
|
|
3026
3025
|
? targetEntry.content
|
|
3027
3026
|
: targetEntry.content
|
|
3028
3027
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
3029
|
-
.map(
|
|
3028
|
+
.map(c => c.text)
|
|
3030
3029
|
.join("");
|
|
3031
3030
|
} else {
|
|
3032
3031
|
// Non-user message: leaf = selected node
|
|
@@ -3092,7 +3091,7 @@ export class AgentSession {
|
|
|
3092
3091
|
if (Array.isArray(content)) {
|
|
3093
3092
|
return content
|
|
3094
3093
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
3095
|
-
.map(
|
|
3094
|
+
.map(c => c.text)
|
|
3096
3095
|
.join("");
|
|
3097
3096
|
}
|
|
3098
3097
|
return "";
|
|
@@ -3103,9 +3102,9 @@ export class AgentSession {
|
|
|
3103
3102
|
*/
|
|
3104
3103
|
getSessionStats(): SessionStats {
|
|
3105
3104
|
const state = this.state;
|
|
3106
|
-
const userMessages = state.messages.filter(
|
|
3107
|
-
const assistantMessages = state.messages.filter(
|
|
3108
|
-
const toolResults = state.messages.filter(
|
|
3105
|
+
const userMessages = state.messages.filter(m => m.role === "user").length;
|
|
3106
|
+
const assistantMessages = state.messages.filter(m => m.role === "assistant").length;
|
|
3107
|
+
const toolResults = state.messages.filter(m => m.role === "toolResult").length;
|
|
3109
3108
|
|
|
3110
3109
|
let toolCalls = 0;
|
|
3111
3110
|
let totalInput = 0;
|
|
@@ -3125,7 +3124,7 @@ export class AgentSession {
|
|
|
3125
3124
|
for (const message of state.messages) {
|
|
3126
3125
|
if (message.role === "assistant") {
|
|
3127
3126
|
const assistantMsg = message as AssistantMessage;
|
|
3128
|
-
toolCalls += assistantMsg.content.filter(
|
|
3127
|
+
toolCalls += assistantMsg.content.filter(c => c.type === "toolCall").length;
|
|
3129
3128
|
totalInput += assistantMsg.usage.input;
|
|
3130
3129
|
totalOutput += assistantMsg.usage.output;
|
|
3131
3130
|
totalCacheRead += assistantMsg.usage.cacheRead;
|
|
@@ -3193,7 +3192,7 @@ export class AgentSession {
|
|
|
3193
3192
|
const authStorage = this._modelRegistry.authStorage;
|
|
3194
3193
|
if (!authStorage.fetchUsageReports) return null;
|
|
3195
3194
|
return authStorage.fetchUsageReports({
|
|
3196
|
-
baseUrlResolver:
|
|
3195
|
+
baseUrlResolver: provider => this._modelRegistry.getProviderBaseUrl?.(provider),
|
|
3197
3196
|
});
|
|
3198
3197
|
}
|
|
3199
3198
|
|
|
@@ -3274,7 +3273,7 @@ export class AgentSession {
|
|
|
3274
3273
|
const lastAssistant = this.messages
|
|
3275
3274
|
.slice()
|
|
3276
3275
|
.reverse()
|
|
3277
|
-
.find(
|
|
3276
|
+
.find(m => {
|
|
3278
3277
|
if (m.role !== "assistant") return false;
|
|
3279
3278
|
const msg = m as AssistantMessage;
|
|
3280
3279
|
// Skip aborted messages with no content
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { getAgentDbPath } from "@oh-my-pi/pi-coding-agent/config";
|
|
5
|
-
import type { Settings } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
6
4
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import { getAgentDbPath } from "../config";
|
|
6
|
+
import type { Settings } from "../config/settings-manager";
|
|
7
7
|
import type { AuthCredential } from "./auth-storage";
|
|
8
8
|
|
|
9
9
|
/** Prepared SQLite statement type from bun:sqlite */
|
|
@@ -116,8 +116,6 @@ export class AgentStorage {
|
|
|
116
116
|
private static instances = new Map<string, AgentStorage>();
|
|
117
117
|
|
|
118
118
|
private listSettingsStmt: Statement;
|
|
119
|
-
private insertSettingStmt: Statement;
|
|
120
|
-
private deleteSettingsStmt: Statement;
|
|
121
119
|
private getCacheStmt: Statement;
|
|
122
120
|
private upsertCacheStmt: Statement;
|
|
123
121
|
private deleteExpiredCacheStmt: Statement;
|
|
@@ -137,10 +135,6 @@ export class AgentStorage {
|
|
|
137
135
|
this.hardenPermissions(dbPath);
|
|
138
136
|
|
|
139
137
|
this.listSettingsStmt = this.db.prepare("SELECT key, value FROM settings");
|
|
140
|
-
this.insertSettingStmt = this.db.prepare(
|
|
141
|
-
"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, unixepoch())",
|
|
142
|
-
);
|
|
143
|
-
this.deleteSettingsStmt = this.db.prepare("DELETE FROM settings");
|
|
144
138
|
|
|
145
139
|
this.getCacheStmt = this.db.prepare("SELECT value FROM cache WHERE key = ? AND expires_at > unixepoch()");
|
|
146
140
|
this.upsertCacheStmt = this.db.prepare(
|
|
@@ -197,8 +191,8 @@ CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
|
|
|
197
191
|
|
|
198
192
|
const settingsInfo = this.db.prepare("PRAGMA table_info(settings)").all() as Array<{ name?: string }>;
|
|
199
193
|
const hasSettingsTable = settingsInfo.length > 0;
|
|
200
|
-
const hasKey = settingsInfo.some(
|
|
201
|
-
const hasValue = settingsInfo.some(
|
|
194
|
+
const hasKey = settingsInfo.some(column => column.name === "key");
|
|
195
|
+
const hasValue = settingsInfo.some(column => column.name === "value");
|
|
202
196
|
|
|
203
197
|
if (!hasSettingsTable) {
|
|
204
198
|
this.db.exec(`
|
|
@@ -264,20 +258,43 @@ CREATE TABLE settings (
|
|
|
264
258
|
|
|
265
259
|
/**
|
|
266
260
|
* Returns singleton instance for the given database path, creating if needed.
|
|
261
|
+
* Retries on SQLITE_BUSY with exponential backoff.
|
|
267
262
|
* @param dbPath - Path to the SQLite database file (defaults to config path)
|
|
268
263
|
* @returns AgentStorage instance for the given path
|
|
269
264
|
*/
|
|
270
|
-
static open(dbPath: string = getAgentDbPath()): AgentStorage {
|
|
265
|
+
static async open(dbPath: string = getAgentDbPath()): Promise<AgentStorage> {
|
|
271
266
|
const existing = AgentStorage.instances.get(dbPath);
|
|
272
267
|
if (existing) return existing;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
268
|
+
|
|
269
|
+
const maxRetries = 3;
|
|
270
|
+
const baseDelayMs = 100;
|
|
271
|
+
let lastError: Error | undefined;
|
|
272
|
+
|
|
273
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
274
|
+
try {
|
|
275
|
+
const storage = new AgentStorage(dbPath);
|
|
276
|
+
AgentStorage.instances.set(dbPath, storage);
|
|
277
|
+
return storage;
|
|
278
|
+
} catch (err) {
|
|
279
|
+
const isSqliteBusy = err && typeof err === "object" && (err as { code?: string }).code === "SQLITE_BUSY";
|
|
280
|
+
if (!isSqliteBusy) {
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
lastError = err as Error;
|
|
284
|
+
const delayMs = baseDelayMs * 2 ** attempt;
|
|
285
|
+
await Bun.sleep(delayMs);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw lastError ?? new Error("Failed to open database after retries");
|
|
276
290
|
}
|
|
277
291
|
|
|
278
292
|
/**
|
|
279
|
-
* Retrieves all settings from storage.
|
|
293
|
+
* Retrieves all settings from storage (legacy, for migration only).
|
|
294
|
+
* Settings are now stored in config.yml. This method is only used
|
|
295
|
+
* during migration from agent.db to config.yml.
|
|
280
296
|
* @returns Settings object, or null if no settings are stored
|
|
297
|
+
* @deprecated Use config.yml instead. This is only for migration.
|
|
281
298
|
*/
|
|
282
299
|
getSettings(): Settings | null {
|
|
283
300
|
const rows = (this.listSettingsStmt.all() as SettingsRow[]) ?? [];
|
|
@@ -297,26 +314,13 @@ CREATE TABLE settings (
|
|
|
297
314
|
}
|
|
298
315
|
|
|
299
316
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* @param settings - Settings object to persist
|
|
317
|
+
* @deprecated Settings are now stored in config.yml, not agent.db.
|
|
318
|
+
* This method is kept for backward compatibility but does nothing.
|
|
303
319
|
*/
|
|
304
320
|
saveSettings(settings: Settings): void {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.deleteSettingsStmt.run();
|
|
308
|
-
for (const [key, value] of rows) {
|
|
309
|
-
const serialized = JSON.stringify(value);
|
|
310
|
-
if (serialized === undefined) continue;
|
|
311
|
-
this.insertSettingStmt.run(key, serialized);
|
|
312
|
-
}
|
|
321
|
+
logger.warn("AgentStorage.saveSettings is deprecated - settings are now stored in config.yml", {
|
|
322
|
+
keys: Object.keys(settings),
|
|
313
323
|
});
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
replace(entries);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
logger.error("AgentStorage failed to save settings", { error: String(error) });
|
|
319
|
-
}
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
/**
|
|
@@ -480,20 +484,20 @@ CREATE TABLE settings (
|
|
|
480
484
|
* @param dbPath - Path to the database file
|
|
481
485
|
*/
|
|
482
486
|
private ensureDir(dbPath: string): void {
|
|
483
|
-
mkdirSync(dirname(dbPath), { recursive: true });
|
|
487
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
484
488
|
}
|
|
485
489
|
|
|
486
490
|
private hardenPermissions(dbPath: string): void {
|
|
487
|
-
const dir = dirname(dbPath);
|
|
491
|
+
const dir = path.dirname(dbPath);
|
|
488
492
|
try {
|
|
489
|
-
chmodSync(dir, 0o700);
|
|
493
|
+
fs.chmodSync(dir, 0o700);
|
|
490
494
|
} catch (error) {
|
|
491
495
|
logger.warn("AgentStorage failed to chmod agent dir", { path: dir, error: String(error) });
|
|
492
496
|
}
|
|
493
497
|
|
|
494
|
-
if (!existsSync(dbPath)) return;
|
|
498
|
+
if (!fs.existsSync(dbPath)) return;
|
|
495
499
|
try {
|
|
496
|
-
chmodSync(dbPath, 0o600);
|
|
500
|
+
fs.chmodSync(dbPath, 0o600);
|
|
497
501
|
} catch (error) {
|
|
498
502
|
logger.warn("AgentStorage failed to chmod db file", { path: dbPath, error: String(error) });
|
|
499
503
|
}
|
package/src/session/artifacts.ts
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Artifacts are stored in a directory alongside the session file,
|
|
5
5
|
* accessible via artifact:// URLs or the $ARTIFACTS environment variable.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import { join } from "node:path";
|
|
7
|
+
import * as fs from "node:fs/promises";
|
|
8
|
+
import * as path from "node:path";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Manages artifact storage for a session.
|
|
@@ -18,6 +17,7 @@ export class ArtifactManager {
|
|
|
18
17
|
#nextId = 0;
|
|
19
18
|
readonly #dir: string;
|
|
20
19
|
#dirCreated = false;
|
|
20
|
+
#initialized = false;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @param sessionFile Path to the session .jsonl file
|
|
@@ -37,9 +37,31 @@ export class ArtifactManager {
|
|
|
37
37
|
|
|
38
38
|
async #ensureDir(): Promise<void> {
|
|
39
39
|
if (!this.#dirCreated) {
|
|
40
|
-
await mkdir(this.#dir, { recursive: true });
|
|
40
|
+
await fs.mkdir(this.#dir, { recursive: true });
|
|
41
41
|
this.#dirCreated = true;
|
|
42
42
|
}
|
|
43
|
+
if (!this.#initialized) {
|
|
44
|
+
await this.#scanExistingIds();
|
|
45
|
+
this.#initialized = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Scan existing artifact files to find the next available ID.
|
|
51
|
+
* This ensures we don't overwrite artifacts when resuming a session.
|
|
52
|
+
*/
|
|
53
|
+
async #scanExistingIds(): Promise<void> {
|
|
54
|
+
const files = await this.listFiles();
|
|
55
|
+
let maxId = -1;
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
// Files are named: {id}.{toolType}.log
|
|
58
|
+
const match = file.match(/^(\d+)\..*\.log$/);
|
|
59
|
+
if (match) {
|
|
60
|
+
const id = parseInt(match[1], 10);
|
|
61
|
+
if (id > maxId) maxId = id;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.#nextId = maxId + 1;
|
|
43
65
|
}
|
|
44
66
|
|
|
45
67
|
/**
|
|
@@ -58,8 +80,8 @@ export class ArtifactManager {
|
|
|
58
80
|
async allocatePath(toolType: string): Promise<{ id: string; path: string }> {
|
|
59
81
|
await this.#ensureDir();
|
|
60
82
|
const id = String(this.allocateId());
|
|
61
|
-
const filename = `${id}.${toolType}.
|
|
62
|
-
return { id, path: join(this.#dir, filename) };
|
|
83
|
+
const filename = `${id}.${toolType}.log`;
|
|
84
|
+
return { id, path: path.join(this.#dir, filename) };
|
|
63
85
|
}
|
|
64
86
|
|
|
65
87
|
/**
|
|
@@ -81,7 +103,7 @@ export class ArtifactManager {
|
|
|
81
103
|
*/
|
|
82
104
|
async exists(id: string): Promise<boolean> {
|
|
83
105
|
const files = await this.listFiles();
|
|
84
|
-
return files.some(
|
|
106
|
+
return files.some(f => f.startsWith(`${id}.`));
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
/**
|
|
@@ -90,7 +112,7 @@ export class ArtifactManager {
|
|
|
90
112
|
*/
|
|
91
113
|
async listFiles(): Promise<string[]> {
|
|
92
114
|
try {
|
|
93
|
-
return await readdir(this.#dir);
|
|
115
|
+
return await fs.readdir(this.#dir);
|
|
94
116
|
} catch {
|
|
95
117
|
return [];
|
|
96
118
|
}
|
|
@@ -104,7 +126,7 @@ export class ArtifactManager {
|
|
|
104
126
|
*/
|
|
105
127
|
async getPath(id: string): Promise<string | null> {
|
|
106
128
|
const files = await this.listFiles();
|
|
107
|
-
const match = files.find(
|
|
108
|
-
return match ? join(this.#dir, match) : null;
|
|
129
|
+
const match = files.find(f => f.startsWith(`${id}.`));
|
|
130
|
+
return match ? path.join(this.#dir, match) : null;
|
|
109
131
|
}
|
|
110
132
|
}
|