@oh-my-pi/pi-coding-agent 15.10.10 → 15.10.12
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 +142 -7
- package/dist/cli.js +23108 -0
- package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
- package/dist/types/async/job-manager.d.ts +18 -0
- package/dist/types/cli/args.d.ts +2 -1
- package/dist/types/cli/dry-balance-cli.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +1 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
- package/dist/types/cli/usage-cli.d.ts +72 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +5 -1
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/usage.d.ts +25 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/append-only-context-mode.d.ts +2 -1
- package/dist/types/config/model-discovery.d.ts +55 -0
- package/dist/types/config/model-registry.d.ts +8 -219
- package/dist/types/config/model-resolver.d.ts +34 -10
- package/dist/types/config/model-roles.d.ts +28 -0
- package/dist/types/config/models-config-schema.d.ts +523 -42
- package/dist/types/config/models-config.d.ts +385 -0
- package/dist/types/config/settings-schema.d.ts +41 -8
- package/dist/types/config/settings.d.ts +8 -1
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/backend.d.ts +0 -2
- package/dist/types/eval/idle-timeout.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +6 -3
- package/dist/types/hindsight/mental-models.d.ts +17 -8
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/lsp/edits.d.ts +9 -0
- package/dist/types/lsp/index.d.ts +2 -2
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +3 -0
- package/dist/types/mcp/json-rpc.d.ts +5 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/mnemopi/state.d.ts +11 -1
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/bash-execution.d.ts +1 -1
- package/dist/types/modes/components/copy-selector.d.ts +1 -1
- package/dist/types/modes/components/dynamic-border.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
- package/dist/types/modes/components/footer.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +5 -0
- package/dist/types/modes/components/hook-input.d.ts +4 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +1 -1
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +25 -6
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/components/visual-truncate.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +19 -3
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +8 -3
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +15 -3
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/index.d.ts +2 -2
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/conflict-detect.d.ts +16 -0
- package/dist/types/tools/github-cache.d.ts +7 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/sqlite-reader.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +3 -3
- package/dist/types/utils/changelog.d.ts +8 -0
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
- package/dist/types/web/scrapers/types.d.ts +12 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +1 -1
- package/examples/extensions/tools.ts +5 -4
- package/package.json +14 -11
- package/scripts/build-binary.ts +18 -23
- package/scripts/bundle-dist.ts +81 -0
- package/scripts/{dev-launch → omp} +1 -1
- package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
- package/src/async/job-manager.ts +57 -3
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/autoresearch/dashboard.ts +1 -1
- package/src/autoresearch/prompt-setup.md +6 -6
- package/src/autoresearch/prompt.md +6 -6
- package/src/capability/fs.ts +10 -0
- package/src/cli/args.ts +4 -1
- package/src/cli/auth-gateway-cli.ts +1 -3
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +1 -1
- package/src/cli/gallery-fixtures/types.ts +5 -1
- package/src/cli/list-models.ts +2 -1
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli-commands.ts +30 -0
- package/src/cli.ts +76 -13
- package/src/commands/complete.ts +1 -1
- package/src/commands/launch.ts +5 -1
- package/src/commands/read.ts +6 -3
- package/src/commands/usage.ts +35 -0
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/model-selection.ts +4 -3
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +320 -1041
- package/src/config/model-resolver.ts +173 -156
- package/src/config/model-roles.ts +74 -0
- package/src/config/models-config-schema.ts +57 -8
- package/src/config/models-config.ts +129 -0
- package/src/config/settings-schema.ts +61 -19
- package/src/config/settings.ts +98 -4
- package/src/dap/client.ts +124 -37
- package/src/dap/session.ts +259 -158
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/raw-sse.ts +1 -1
- package/src/edit/diff.ts +47 -3
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +36 -1
- package/src/edit/hashline/execute.ts +47 -4
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/index.ts +16 -1
- package/src/edit/modes/patch.ts +52 -0
- package/src/edit/modes/replace.ts +56 -22
- package/src/edit/notebook.ts +22 -2
- package/src/edit/renderer.ts +36 -10
- package/src/eval/__tests__/completion-bridge.test.ts +1 -1
- package/src/eval/backend.ts +0 -2
- package/src/eval/completion-bridge.ts +3 -1
- package/src/eval/idle-timeout.ts +2 -9
- package/src/eval/js/context-manager.ts +6 -8
- package/src/eval/js/executor.ts +6 -2
- package/src/eval/js/index.ts +0 -2
- package/src/eval/js/shared/helpers.ts +5 -6
- package/src/eval/js/shared/local-module-loader.ts +1 -1
- package/src/eval/js/shared/prelude.txt +62 -1
- package/src/eval/js/shared/rewrite-imports.ts +40 -22
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -3
- package/src/eval/py/kernel.ts +43 -4
- package/src/eval/py/runner.py +107 -3
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +85 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +6 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
- package/src/hindsight/bank.ts +17 -2
- package/src/hindsight/mental-models.ts +59 -12
- package/src/hindsight/state.ts +6 -1
- package/src/internal-urls/artifact-protocol.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/issue-pr-protocol.ts +12 -5
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/lib/xai-http.ts +1 -1
- package/src/lsp/client.ts +118 -38
- package/src/lsp/clients/biome-client.ts +101 -39
- package/src/lsp/edits.ts +143 -95
- package/src/lsp/index.ts +31 -22
- package/src/lsp/render.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/lsp/utils.ts +28 -10
- package/src/main.ts +183 -23
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/transports/stdio.ts +7 -1
- package/src/memories/index.ts +4 -1
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +176 -7
- package/src/mnemopi/state.ts +38 -2
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/agent-dashboard.ts +10 -7
- package/src/modes/components/assistant-message.ts +32 -28
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/copy-selector.ts +1 -1
- package/src/modes/components/diff.ts +13 -2
- package/src/modes/components/dynamic-border.ts +12 -3
- package/src/modes/components/extensions/extension-dashboard.ts +8 -5
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +8 -0
- package/src/modes/components/hook-input.ts +8 -0
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-selector.ts +4 -2
- package/src/modes/components/plan-review-overlay.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-selector.ts +5 -1
- package/src/modes/components/status-line/component.ts +119 -35
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/transcript-container.ts +258 -53
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +17 -5
- package/src/modes/components/visual-truncate.ts +1 -1
- package/src/modes/components/welcome.ts +108 -26
- package/src/modes/controllers/command-controller.ts +11 -4
- package/src/modes/controllers/event-controller.ts +73 -4
- package/src/modes/controllers/input-controller.ts +2 -1
- package/src/modes/controllers/mcp-command-controller.ts +39 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/controllers/streaming-reveal.ts +85 -18
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +42 -18
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
- package/src/modes/setup-wizard/scenes/providers.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
- package/src/modes/setup-wizard/scenes/theme.ts +1 -1
- package/src/modes/setup-wizard/scenes/types.ts +1 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +4 -1
- package/src/prompts/agents/explore.md +2 -2
- package/src/prompts/agents/librarian.md +1 -2
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +5 -5
- package/src/prompts/agents/task.md +5 -5
- package/src/prompts/ci-green-request.md +5 -7
- package/src/prompts/goals/goal-budget-limit.md +2 -2
- package/src/prompts/goals/goal-continuation.md +4 -4
- package/src/prompts/goals/goal-mode-active.md +1 -1
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_system.md +2 -2
- package/src/prompts/review-custom-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/background-tan-dispatch.md +1 -1
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +13 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-todo.md +2 -2
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/system/manual-continue.md +1 -1
- package/src/prompts/system/omfg-user.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +9 -9
- package/src/prompts/system/plan-mode-active.md +4 -4
- package/src/prompts/system/plan-mode-subagent.md +4 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +13 -24
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +2 -2
- package/src/prompts/tools/bash.md +5 -7
- package/src/prompts/tools/browser.md +7 -7
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/find.md +0 -1
- package/src/prompts/tools/github.md +8 -7
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/irc.md +15 -15
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/read.md +3 -4
- package/src/prompts/tools/recall.md +1 -1
- package/src/prompts/tools/reflect.md +1 -1
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/replace.md +4 -10
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +1 -9
- package/src/prompts/tools/search.md +0 -1
- package/src/prompts/tools/ssh.md +0 -4
- package/src/prompts/tools/task.md +2 -3
- package/src/prompts/tools/todo.md +1 -1
- package/src/sdk.ts +31 -11
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +223 -64
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/session-manager.ts +2 -2
- package/src/session/streaming-output.ts +188 -11
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +40 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/ssh/connection-manager.ts +27 -0
- package/src/system-prompt.ts +14 -0
- package/src/task/commands.ts +2 -1
- package/src/task/executor.ts +74 -65
- package/src/task/index.ts +146 -68
- package/src/task/parallel.ts +3 -3
- package/src/task/render.ts +20 -5
- package/src/task/types.ts +9 -0
- package/src/task/worktree.ts +64 -56
- package/src/thinking.ts +9 -1
- package/src/tiny/title-client.ts +60 -16
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/archive-reader.ts +30 -2
- package/src/tools/ask.ts +104 -21
- package/src/tools/ast-edit.ts +25 -5
- package/src/tools/auto-generated-guard.ts +20 -3
- package/src/tools/bash-interactive.ts +27 -7
- package/src/tools/bash.ts +100 -18
- package/src/tools/browser/launch.ts +11 -2
- package/src/tools/browser/readable.ts +19 -2
- package/src/tools/browser/registry.ts +4 -1
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +55 -16
- package/src/tools/conflict-detect.ts +50 -4
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +5 -5
- package/src/tools/eval.ts +0 -2
- package/src/tools/fetch.ts +33 -10
- package/src/tools/gh-cache-invalidation.ts +63 -8
- package/src/tools/gh-renderer.ts +1 -1
- package/src/tools/gh.ts +172 -29
- package/src/tools/github-cache.ts +70 -6
- package/src/tools/image-gen.ts +14 -13
- package/src/tools/index.ts +13 -1
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +5 -1
- package/src/tools/job.ts +1 -1
- package/src/tools/read.ts +202 -61
- package/src/tools/render-utils.ts +3 -3
- package/src/tools/resolve.ts +1 -1
- package/src/tools/search.ts +92 -29
- package/src/tools/sqlite-reader.ts +17 -5
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +38 -8
- package/src/tools/write.ts +118 -18
- package/src/tui/output-block.ts +4 -4
- package/src/utils/changelog.ts +27 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/file-mentions.ts +2 -1
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/twitter.ts +2 -1
- package/src/web/scrapers/types.ts +87 -8
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/scrapers/youtube.ts +6 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/providers/codex.ts +2 -1
- package/src/web/search/providers/gemini.ts +2 -3
- package/src/web/search/render.ts +8 -6
- package/dist/types/config/model-equivalence.d.ts +0 -24
- package/dist/types/config/model-id-affixes.d.ts +0 -12
- package/dist/types/config/model-provider-priority.d.ts +0 -1
- package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
- package/src/config/model-equivalence.ts +0 -875
- package/src/config/model-id-affixes.ts +0 -81
- package/src/config/model-provider-priority.ts +0 -56
- package/src/exec/idle-timeout-watchdog.ts +0 -126
|
@@ -2,9 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { $which, getPuppeteerDir, logger } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import * as
|
|
5
|
+
import type * as BrowsersNs from "@puppeteer/browsers";
|
|
6
6
|
import type { Browser, CDPSession, Page, default as Puppeteer, Target } from "puppeteer-core";
|
|
7
|
-
import { PUPPETEER_REVISIONS } from "puppeteer-core/internal/revisions.js";
|
|
8
7
|
import stealthTamperingScript from "../puppeteer/00_stealth_tampering.txt" with { type: "text" };
|
|
9
8
|
import stealthActivityScript from "../puppeteer/01_stealth_activity.txt" with { type: "text" };
|
|
10
9
|
import stealthHairlineScript from "../puppeteer/02_stealth_hairline.txt" with { type: "text" };
|
|
@@ -78,6 +77,14 @@ export async function loadPuppeteerInWorker(safeDir: string): Promise<typeof Pup
|
|
|
78
77
|
}
|
|
79
78
|
}
|
|
80
79
|
|
|
80
|
+
let browsersModule: typeof BrowsersNs | undefined;
|
|
81
|
+
async function loadBrowsers(): Promise<typeof BrowsersNs> {
|
|
82
|
+
if (!browsersModule) {
|
|
83
|
+
browsersModule = await import("@puppeteer/browsers");
|
|
84
|
+
}
|
|
85
|
+
return browsersModule;
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
/**
|
|
82
89
|
* Lazily download Chromium on first browser launch via @puppeteer/browsers.
|
|
83
90
|
* Skipped when a system Chromium (NixOS) or PUPPETEER_EXECUTABLE_PATH is set.
|
|
@@ -92,12 +99,14 @@ async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
|
92
99
|
if (chromiumExecutablePromise) return chromiumExecutablePromise;
|
|
93
100
|
|
|
94
101
|
chromiumExecutablePromise = (async () => {
|
|
102
|
+
const browsers = await loadBrowsers();
|
|
95
103
|
const platform = browsers.detectBrowserPlatform();
|
|
96
104
|
if (!platform) {
|
|
97
105
|
logger.warn("Could not detect browser platform; relying on puppeteer default resolution");
|
|
98
106
|
return undefined;
|
|
99
107
|
}
|
|
100
108
|
const cacheDir = getPuppeteerDir();
|
|
109
|
+
const { PUPPETEER_REVISIONS } = await import("puppeteer-core/internal/revisions.js");
|
|
101
110
|
const buildId = await browsers.resolveBuildId(browsers.Browser.CHROME, platform, PUPPETEER_REVISIONS.chrome);
|
|
102
111
|
const executablePath = browsers.computeExecutablePath({
|
|
103
112
|
browser: browsers.Browser.CHROME,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import type * as ReadabilityNs from "@mozilla/readability";
|
|
2
|
+
import type * as LinkedomNs from "linkedom";
|
|
3
3
|
import { htmlToBasicMarkdown } from "../../web/scrapers/types";
|
|
4
4
|
|
|
5
5
|
export type ReadableFormat = "text" | "markdown";
|
|
@@ -20,6 +20,22 @@ function normalize(text: string | null | undefined): string | undefined {
|
|
|
20
20
|
return trimmed || undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
let readabilityModule: typeof ReadabilityNs | undefined;
|
|
24
|
+
async function loadReadability(): Promise<typeof ReadabilityNs> {
|
|
25
|
+
if (!readabilityModule) {
|
|
26
|
+
readabilityModule = await import("@mozilla/readability");
|
|
27
|
+
}
|
|
28
|
+
return readabilityModule;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let linkedomModule: typeof LinkedomNs | undefined;
|
|
32
|
+
async function loadLinkedom(): Promise<typeof LinkedomNs> {
|
|
33
|
+
if (!linkedomModule) {
|
|
34
|
+
linkedomModule = await import("linkedom");
|
|
35
|
+
}
|
|
36
|
+
return linkedomModule;
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
/**
|
|
24
40
|
* Extract readable content from raw HTML.
|
|
25
41
|
* Tries Readability (article-isolation scoring) first, then falls back to a
|
|
@@ -31,6 +47,7 @@ export async function extractReadableFromHtml(
|
|
|
31
47
|
url: string,
|
|
32
48
|
format: ReadableFormat,
|
|
33
49
|
): Promise<ReadableResult | null> {
|
|
50
|
+
const [{ parseHTML }, { Readability }] = await Promise.all([loadLinkedom(), loadReadability()]);
|
|
34
51
|
const { document } = parseHTML(html);
|
|
35
52
|
|
|
36
53
|
// --- Primary: Readability article extraction ---
|
|
@@ -157,7 +157,10 @@ export function holdBrowser(handle: BrowserHandle): void {
|
|
|
157
157
|
export async function releaseBrowser(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
|
|
158
158
|
handle.refCount = Math.max(0, handle.refCount - 1);
|
|
159
159
|
if (handle.refCount === 0) {
|
|
160
|
-
|
|
160
|
+
// Only evict if the registry still points at THIS handle. After a disconnect,
|
|
161
|
+
// `acquireBrowser` may have already replaced the entry with a fresh live handle
|
|
162
|
+
// under the same key; deleting blindly would orphan that new browser.
|
|
163
|
+
if (browsers.get(handle.key) === handle) browsers.delete(handle.key);
|
|
161
164
|
await disposeBrowserHandle(handle, opts);
|
|
162
165
|
}
|
|
163
166
|
}
|
|
@@ -66,7 +66,7 @@ function dropTrailingBlankLines(text: string): string {
|
|
|
66
66
|
function appendLine(component: Component, line: string | undefined): Component {
|
|
67
67
|
if (!line) return component;
|
|
68
68
|
const wrapped = {
|
|
69
|
-
render: (width: number): string[] => {
|
|
69
|
+
render: (width: number): readonly string[] => {
|
|
70
70
|
const base = component.render(width);
|
|
71
71
|
return [...base, line];
|
|
72
72
|
},
|
|
@@ -95,7 +95,7 @@ function renderRunCell(
|
|
|
95
95
|
|
|
96
96
|
let cached: { key: bigint; width: number; lines: string[] } | undefined;
|
|
97
97
|
return markFramedBlockComponent({
|
|
98
|
-
render: (width: number): string[] => {
|
|
98
|
+
render: (width: number): readonly string[] => {
|
|
99
99
|
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
100
100
|
const previewLines = options.renderContext?.previewLines ?? BROWSER_DEFAULT_PREVIEW_LINES;
|
|
101
101
|
const key = new Hasher()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getPuppeteerDir,
|
|
1
|
+
import { getPuppeteerDir, logger, Snowflake, workerHostEntry } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import type { Page, Target } from "puppeteer-core";
|
|
3
3
|
import { callSessionTool } from "../../eval/js/tool-bridge";
|
|
4
4
|
import type { ToolSession } from "../../sdk";
|
|
@@ -18,14 +18,8 @@ import type {
|
|
|
18
18
|
WorkerOutbound,
|
|
19
19
|
} from "./tab-protocol";
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// (registered as an additional entrypoint in `scripts/build-binary.ts`); in
|
|
24
|
-
// dev we resolve the same source via `import.meta.url`. Replaces the older
|
|
25
|
-
// `with { type: "file" }` pattern, which only copied the entry as a raw
|
|
26
|
-
// asset and could not resolve the worker's relative imports inside a
|
|
27
|
-
// compiled binary (issue #1011 was a false-positive fix — the regression
|
|
28
|
-
// test only checked emission, not actual worker startup).
|
|
21
|
+
// Coding-agent binary/bundle workers route through the CLI entrypoint with a
|
|
22
|
+
// hidden argv mode, so compiled/npm builds only need one JavaScript entry.
|
|
29
23
|
|
|
30
24
|
interface WorkerHandle {
|
|
31
25
|
send(msg: WorkerInbound, transferList?: Transferable[]): void;
|
|
@@ -84,21 +78,51 @@ export interface ReleaseTabOptions {
|
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
const tabs = new Map<string, TabSession>();
|
|
81
|
+
// Per-name acquisition chain: serializes concurrent `acquireTab` calls for the
|
|
82
|
+
// same tab name so the existence check and `tabs.set` (separated by several
|
|
83
|
+
// awaits) cannot interleave and leak a worker + browser refCount.
|
|
84
|
+
const acquireChains = new Map<string, Promise<void>>();
|
|
87
85
|
const GRACE_MS = 750;
|
|
88
86
|
|
|
89
87
|
export function getTab(name: string): TabSession | undefined {
|
|
90
88
|
return tabs.get(name);
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
export
|
|
91
|
+
export function acquireTab(name: string, browser: BrowserHandle, opts: AcquireTabOptions): Promise<AcquireTabResult> {
|
|
92
|
+
const prior = acquireChains.get(name) ?? Promise.resolve();
|
|
93
|
+
const result = prior.then(() => acquireTabImpl(name, browser, opts));
|
|
94
|
+
const tail = result.then(
|
|
95
|
+
() => undefined,
|
|
96
|
+
() => undefined,
|
|
97
|
+
);
|
|
98
|
+
acquireChains.set(name, tail);
|
|
99
|
+
void tail.then(() => {
|
|
100
|
+
if (acquireChains.get(name) === tail) acquireChains.delete(name);
|
|
101
|
+
});
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function acquireTabImpl(
|
|
94
106
|
name: string,
|
|
95
107
|
browser: BrowserHandle,
|
|
96
108
|
opts: AcquireTabOptions,
|
|
97
109
|
): Promise<AcquireTabResult> {
|
|
110
|
+
// Serialized opens can sit behind a slow predecessor in the per-name
|
|
111
|
+
// chain; honor an abort at dequeue instead of spawning a worker and
|
|
112
|
+
// browser hold nobody is waiting for.
|
|
113
|
+
if (opts.signal?.aborted) {
|
|
114
|
+
throw new ToolAbortError("Browser tab open aborted");
|
|
115
|
+
}
|
|
116
|
+
// Temporary refCount hold so releasing an existing tab on the SAME browser
|
|
117
|
+
// below cannot drop it to refCount 0 and dispose the instance we are about
|
|
118
|
+
// to reuse (e.g. reopening the sole tab with a different dialogs policy).
|
|
119
|
+
let tempHold = false;
|
|
98
120
|
const existing = tabs.get(name);
|
|
99
121
|
if (existing) {
|
|
100
122
|
if (existing.browser === browser && existing.state === "alive") {
|
|
101
123
|
if (opts.dialogs !== undefined && opts.dialogs !== existing.dialogPolicy) {
|
|
124
|
+
holdBrowser(browser);
|
|
125
|
+
tempHold = true;
|
|
102
126
|
await releaseTab(name, { kill: false });
|
|
103
127
|
} else {
|
|
104
128
|
const reuseSteps: string[] = [];
|
|
@@ -127,12 +151,25 @@ export async function acquireTab(
|
|
|
127
151
|
return { tab: tabs.get(name)!, created: false };
|
|
128
152
|
}
|
|
129
153
|
} else {
|
|
154
|
+
if (existing.browser === browser) {
|
|
155
|
+
holdBrowser(browser);
|
|
156
|
+
tempHold = true;
|
|
157
|
+
}
|
|
130
158
|
await releaseTab(name, { kill: false });
|
|
131
159
|
}
|
|
132
160
|
}
|
|
133
161
|
|
|
134
|
-
|
|
135
|
-
let worker
|
|
162
|
+
let initPayload: WorkerInitPayload;
|
|
163
|
+
let worker: WorkerHandle;
|
|
164
|
+
try {
|
|
165
|
+
initPayload = await buildInitPayload(browser, opts);
|
|
166
|
+
worker = await spawnTabWorker();
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// Failing before the worker took its own hold must release the
|
|
169
|
+
// temporary one, or the browser's refCount never reaches 0 again.
|
|
170
|
+
if (tempHold || browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
136
173
|
let info: ReadyInfo;
|
|
137
174
|
try {
|
|
138
175
|
info = await initializeTabWorker(worker, initPayload, opts.timeoutMs + GRACE_MS);
|
|
@@ -142,7 +179,7 @@ export async function acquireTab(
|
|
|
142
179
|
// the inline worker here so module-resolution failures don't poison every tab open.
|
|
143
180
|
await worker.terminate().catch(() => undefined);
|
|
144
181
|
if (worker.mode === "inline") {
|
|
145
|
-
if (browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
182
|
+
if (tempHold || browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
146
183
|
throw error;
|
|
147
184
|
}
|
|
148
185
|
logger.warn("Tab worker init failed; retrying with inline tab worker (no sync-loop guard)", {
|
|
@@ -153,7 +190,7 @@ export async function acquireTab(
|
|
|
153
190
|
info = await initializeTabWorker(worker, initPayload, opts.timeoutMs + GRACE_MS);
|
|
154
191
|
} catch (inlineError) {
|
|
155
192
|
await worker.terminate().catch(() => undefined);
|
|
156
|
-
if (browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
193
|
+
if (tempHold || browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
157
194
|
const finalError = new ToolError(
|
|
158
195
|
`Failed to start browser tab worker (inline fallback also failed): ${inlineError instanceof Error ? inlineError.message : String(inlineError)}`,
|
|
159
196
|
);
|
|
@@ -163,6 +200,7 @@ export async function acquireTab(
|
|
|
163
200
|
}
|
|
164
201
|
|
|
165
202
|
holdBrowser(browser);
|
|
203
|
+
if (tempHold) await releaseBrowser(browser, { kill: false });
|
|
166
204
|
const tab: TabSession = {
|
|
167
205
|
name,
|
|
168
206
|
browser,
|
|
@@ -474,8 +512,9 @@ async function raceWithTimeout<T>(
|
|
|
474
512
|
|
|
475
513
|
async function spawnTabWorker(): Promise<WorkerHandle> {
|
|
476
514
|
try {
|
|
477
|
-
const
|
|
478
|
-
|
|
515
|
+
const hostEntry = workerHostEntry();
|
|
516
|
+
const worker = hostEntry
|
|
517
|
+
? new Worker(hostEntry, { type: "module", argv: ["__omp_tab_worker"] })
|
|
479
518
|
: new Worker(new URL("./tab-worker-entry.ts", import.meta.url).href, { type: "module" });
|
|
480
519
|
return wrapBunWorker(worker);
|
|
481
520
|
} catch (err) {
|
|
@@ -68,7 +68,9 @@ export function scanConflictLines(lines: readonly string[], firstLineNumber: num
|
|
|
68
68
|
} | null = null;
|
|
69
69
|
|
|
70
70
|
for (let i = 0; i < lines.length; i++) {
|
|
71
|
-
|
|
71
|
+
// Strip a trailing \r so CRLF checkouts match the same markers; stored
|
|
72
|
+
// section lines are LF-normalized (splice re-applies \r on write).
|
|
73
|
+
const line = stripTrailingCr(lines[i]);
|
|
72
74
|
const ln = firstLineNumber + i;
|
|
73
75
|
|
|
74
76
|
const oursLabel = matchMarker(line, OURS_PREFIX);
|
|
@@ -338,13 +340,22 @@ export function spliceConflict(originalText: string, entry: ConflictEntry, repla
|
|
|
338
340
|
}
|
|
339
341
|
|
|
340
342
|
const trimmed = normalizeTrailingNewline(replacement);
|
|
341
|
-
|
|
343
|
+
let replacementLines = trimmed.split("\n").map(stripTrailingCr);
|
|
344
|
+
// Round-trip fidelity for CRLF files: recorded sections are LF-normalized,
|
|
345
|
+
// so re-apply \r to spliced lines when the matched region used CRLF. The
|
|
346
|
+
// final replacement line only carries \r when another line follows it.
|
|
347
|
+
if (lines[match.startIdx]!.endsWith("\r")) {
|
|
348
|
+
const hasFollowingLine = match.endIdx + 1 < lines.length;
|
|
349
|
+
replacementLines = replacementLines.map((l, i) =>
|
|
350
|
+
i < replacementLines.length - 1 || hasFollowingLine ? `${l}\r` : l,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
342
353
|
const next = [...lines.slice(0, match.startIdx), ...replacementLines, ...lines.slice(match.endIdx + 1)];
|
|
343
354
|
return next.join("\n");
|
|
344
355
|
}
|
|
345
356
|
|
|
346
357
|
/** Reconstruct the recorded marker block as it should appear in the file. */
|
|
347
|
-
function buildRecordedRegion(entry:
|
|
358
|
+
function buildRecordedRegion(entry: ConflictBlock): string[] {
|
|
348
359
|
const out: string[] = [];
|
|
349
360
|
out.push(entry.oursLabel ? `${OURS_PREFIX} ${entry.oursLabel}` : OURS_PREFIX);
|
|
350
361
|
out.push(...entry.oursLines);
|
|
@@ -358,6 +369,36 @@ function buildRecordedRegion(entry: ConflictEntry): string[] {
|
|
|
358
369
|
return out;
|
|
359
370
|
}
|
|
360
371
|
|
|
372
|
+
/**
|
|
373
|
+
* True when two registered blocks record the same marker-block content
|
|
374
|
+
* (labels and all sides). Out-of-band edits can shift a block's line
|
|
375
|
+
* numbers between reads, registering a fresh id while the stale one
|
|
376
|
+
* persists; callers use content identity to treat a locate-miss for the
|
|
377
|
+
* stale twin as "already resolved" instead of a hard failure.
|
|
378
|
+
*/
|
|
379
|
+
export function conflictRegionsEqual(a: ConflictBlock, b: ConflictBlock): boolean {
|
|
380
|
+
const ra = buildRecordedRegion(a);
|
|
381
|
+
const rb = buildRecordedRegion(b);
|
|
382
|
+
if (ra.length !== rb.length) return false;
|
|
383
|
+
for (let i = 0; i < ra.length; i++) {
|
|
384
|
+
if (ra[i] !== rb[i]) return false;
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* True when the entry's recorded marker block still occurs in `content`
|
|
391
|
+
* (LF-normalized — recorded sections are stored LF). Distinguishes a stale
|
|
392
|
+
* re-registration of a just-resolved region (no longer present) from a
|
|
393
|
+
* DISTINCT conflict block that happens to be byte-identical (still present
|
|
394
|
+
* elsewhere in the file and must stay addressable).
|
|
395
|
+
*/
|
|
396
|
+
export function conflictRegionPresent(content: string, entry: ConflictBlock): boolean {
|
|
397
|
+
const region = buildRecordedRegion(entry).join("\n");
|
|
398
|
+
const normalized = content.includes("\r") ? content.replace(/\r\n/g, "\n") : content;
|
|
399
|
+
return normalized.includes(region);
|
|
400
|
+
}
|
|
401
|
+
|
|
361
402
|
/**
|
|
362
403
|
* Find a contiguous match of `expected` inside `lines`, preferring the
|
|
363
404
|
* occurrence closest to `preferredIdx` to disambiguate when an identical
|
|
@@ -391,11 +432,16 @@ function locateRegion(
|
|
|
391
432
|
function matchesAt(lines: readonly string[], startIdx: number, expected: readonly string[]): boolean {
|
|
392
433
|
if (startIdx < 0 || startIdx + expected.length > lines.length) return false;
|
|
393
434
|
for (let i = 0; i < expected.length; i++) {
|
|
394
|
-
|
|
435
|
+
// Recorded lines are LF-normalized; tolerate CRLF on-disk lines.
|
|
436
|
+
if (stripTrailingCr(lines[startIdx + i]!) !== expected[i]) return false;
|
|
395
437
|
}
|
|
396
438
|
return true;
|
|
397
439
|
}
|
|
398
440
|
|
|
441
|
+
function stripTrailingCr(line: string): string {
|
|
442
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
443
|
+
}
|
|
444
|
+
|
|
399
445
|
function normalizeTrailingNewline(replacement: string): string {
|
|
400
446
|
if (replacement.endsWith("\r\n")) return replacement.slice(0, -2);
|
|
401
447
|
if (replacement.endsWith("\n")) return replacement.slice(0, -1);
|
package/src/tools/debug.ts
CHANGED
|
@@ -592,7 +592,7 @@ export const debugToolRenderer = {
|
|
|
592
592
|
): Component {
|
|
593
593
|
const outputBlock = new CachedOutputBlock();
|
|
594
594
|
return markFramedBlockComponent({
|
|
595
|
-
render(width: number): string[] {
|
|
595
|
+
render(width: number): readonly string[] {
|
|
596
596
|
const action = (args?.action ?? result.details?.action ?? "debug").replaceAll("_", " ");
|
|
597
597
|
const success = !options.isPartial && !result.isError;
|
|
598
598
|
const statusIcon = success
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -455,7 +455,7 @@ function formatCellOutputLines(
|
|
|
455
455
|
previewLines: number,
|
|
456
456
|
theme: Theme,
|
|
457
457
|
width: number,
|
|
458
|
-
): { lines: string[]; hiddenCount: number } {
|
|
458
|
+
): { lines: readonly string[]; hiddenCount: number } {
|
|
459
459
|
if (!cell.output) {
|
|
460
460
|
return { lines: [], hiddenCount: 0 };
|
|
461
461
|
}
|
|
@@ -492,7 +492,7 @@ export const evalToolRenderer = {
|
|
|
492
492
|
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
493
493
|
|
|
494
494
|
return markFramedBlockComponent({
|
|
495
|
-
render: (width: number): string[] => {
|
|
495
|
+
render: (width: number): readonly string[] => {
|
|
496
496
|
const key = `${options.expanded ? 1 : 0}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
|
|
497
497
|
if (cached && cached.key === key && cached.width === width) {
|
|
498
498
|
return cached.result;
|
|
@@ -573,7 +573,7 @@ export const evalToolRenderer = {
|
|
|
573
573
|
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
574
574
|
|
|
575
575
|
return markFramedBlockComponent({
|
|
576
|
-
render: (width: number): string[] => {
|
|
576
|
+
render: (width: number): readonly string[] => {
|
|
577
577
|
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
578
578
|
const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
|
|
579
579
|
const key = `${expanded}|${previewLines}|${options.spinnerFrame}`;
|
|
@@ -697,12 +697,12 @@ export const evalToolRenderer = {
|
|
|
697
697
|
const textContent = `\n${styledOutput}`;
|
|
698
698
|
|
|
699
699
|
let cachedWidth: number | undefined;
|
|
700
|
-
let cachedLines: string[] | undefined;
|
|
700
|
+
let cachedLines: readonly string[] | undefined;
|
|
701
701
|
let cachedSkipped: number | undefined;
|
|
702
702
|
let cachedPreviewLines: number | undefined;
|
|
703
703
|
|
|
704
704
|
return {
|
|
705
|
-
render: (width: number): string[] => {
|
|
705
|
+
render: (width: number): readonly string[] => {
|
|
706
706
|
const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
|
|
707
707
|
if (cachedLines === undefined || cachedWidth !== width || cachedPreviewLines !== previewLines) {
|
|
708
708
|
const result = truncateToVisualLines(textContent, previewLines, width);
|
package/src/tools/eval.ts
CHANGED
package/src/tools/fetch.ts
CHANGED
|
@@ -7,7 +7,6 @@ import type { FetchImpl, ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
|
7
7
|
import { htmlToMarkdown } from "@oh-my-pi/pi-natives";
|
|
8
8
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import { $which, ptree, truncate } from "@oh-my-pi/pi-utils";
|
|
10
|
-
import { parseHTML } from "linkedom";
|
|
11
10
|
import { LRUCache } from "lru-cache/raw";
|
|
12
11
|
import type { Settings } from "../config/settings";
|
|
13
12
|
import { readEditableNotebookText } from "../edit/notebook";
|
|
@@ -23,7 +22,7 @@ import { ensureTool } from "../utils/tools-manager";
|
|
|
23
22
|
import { extractWithParallel, findParallelApiKey, getParallelExtractContent } from "../web/parallel";
|
|
24
23
|
import { specialHandlers } from "../web/scrapers";
|
|
25
24
|
import type { RenderResult } from "../web/scrapers/types";
|
|
26
|
-
import { finalizeOutput, loadPage, looksLikeHtml, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
|
25
|
+
import { finalizeOutput, loadPage, looksLikeHtml, MAX_BYTES, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
|
27
26
|
import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
|
|
28
27
|
import { type ArchiveFormat, listArchiveRoot, sniffArchiveFormat } from "./archive-reader";
|
|
29
28
|
import { applyListLimit } from "./list-limit";
|
|
@@ -191,7 +190,7 @@ export interface ParsedReadUrlTarget {
|
|
|
191
190
|
|
|
192
191
|
/** Recognize a single selector token (`raw` or one/many line ranges). */
|
|
193
192
|
function isUrlSelectorToken(token: string): boolean {
|
|
194
|
-
if (token === "raw") return true;
|
|
193
|
+
if (token.toLowerCase() === "raw") return true;
|
|
195
194
|
try {
|
|
196
195
|
return parseLineRanges(token) !== null;
|
|
197
196
|
} catch {
|
|
@@ -213,7 +212,7 @@ export function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null
|
|
|
213
212
|
let raw = false;
|
|
214
213
|
let ranges: readonly LineRange[] | undefined;
|
|
215
214
|
for (const sel of embedded?.sels ?? []) {
|
|
216
|
-
if (sel === "raw") {
|
|
215
|
+
if (sel.toLowerCase() === "raw") {
|
|
217
216
|
raw = true;
|
|
218
217
|
continue;
|
|
219
218
|
}
|
|
@@ -549,7 +548,8 @@ function cleanFeedText(text: string): string {
|
|
|
549
548
|
/**
|
|
550
549
|
* Parse RSS/Atom feed to markdown
|
|
551
550
|
*/
|
|
552
|
-
function parseFeedToMarkdown(content: string, maxItems = 10): string {
|
|
551
|
+
async function parseFeedToMarkdown(content: string, maxItems = 10): Promise<string> {
|
|
552
|
+
const { parseHTML } = await import("linkedom");
|
|
553
553
|
try {
|
|
554
554
|
const doc = parseHTML(content).document;
|
|
555
555
|
|
|
@@ -805,6 +805,21 @@ function isArchiveHint(mime: string, extensionHint: string): boolean {
|
|
|
805
805
|
return ARCHIVE_MIMES.has(mime) || ARCHIVE_EXTENSIONS.has(extensionHint);
|
|
806
806
|
}
|
|
807
807
|
|
|
808
|
+
/**
|
|
809
|
+
* Content types whose payload renderUrl always re-fetches via fetchBinary.
|
|
810
|
+
* Skipping the initial body read for them avoids downloading and
|
|
811
|
+
* string-decoding huge binaries (PDFs, archives, images) twice.
|
|
812
|
+
*/
|
|
813
|
+
function shouldSkipBodyDownload(contentType: string): boolean {
|
|
814
|
+
return (
|
|
815
|
+
CONVERTIBLE_MIMES.has(contentType) ||
|
|
816
|
+
NOTEBOOK_MIMES.has(contentType) ||
|
|
817
|
+
SQLITE_MIMES.has(contentType) ||
|
|
818
|
+
ARCHIVE_MIMES.has(contentType) ||
|
|
819
|
+
SUPPORTED_INLINE_IMAGE_MIME_TYPES.has(contentType)
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
|
|
808
823
|
function getArchiveFormatHint(mime: string, extensionHint: string): ArchiveFormat | undefined {
|
|
809
824
|
if (extensionHint === ".zip" || mime === "application/zip" || mime === "application/x-zip-compressed") {
|
|
810
825
|
return "zip";
|
|
@@ -901,6 +916,7 @@ async function tryRenderBinaryPayload(
|
|
|
901
916
|
mime: string,
|
|
902
917
|
extHint: string,
|
|
903
918
|
rawContent: string,
|
|
919
|
+
bodySkipped: boolean,
|
|
904
920
|
timeout: number,
|
|
905
921
|
signal: AbortSignal | undefined,
|
|
906
922
|
fetchedAt: string,
|
|
@@ -909,7 +925,7 @@ async function tryRenderBinaryPayload(
|
|
|
909
925
|
const hasNotebookHint = isNotebookHint(mime, extHint);
|
|
910
926
|
const hasSqliteHint = isSqliteHint(mime, extHint);
|
|
911
927
|
const hasArchiveHint = isArchiveHint(mime, extHint);
|
|
912
|
-
const rawLooksBinary = sampleLooksBinary(rawContent);
|
|
928
|
+
const rawLooksBinary = bodySkipped || sampleLooksBinary(rawContent);
|
|
913
929
|
if (!hasNotebookHint && !hasSqliteHint && !hasArchiveHint && !rawLooksBinary) {
|
|
914
930
|
return null;
|
|
915
931
|
}
|
|
@@ -1092,7 +1108,7 @@ async function renderUrl(
|
|
|
1092
1108
|
}
|
|
1093
1109
|
|
|
1094
1110
|
// Step 2: Fetch page
|
|
1095
|
-
const response = await loadPage(url, { timeout, signal });
|
|
1111
|
+
const response = await loadPage(url, { timeout, signal, skipBodyForContentType: shouldSkipBodyDownload });
|
|
1096
1112
|
if (signal?.aborted) {
|
|
1097
1113
|
throw new ToolAbortError();
|
|
1098
1114
|
}
|
|
@@ -1105,11 +1121,17 @@ async function renderUrl(
|
|
|
1105
1121
|
content: "",
|
|
1106
1122
|
fetchedAt,
|
|
1107
1123
|
truncated: false,
|
|
1108
|
-
notes: [
|
|
1124
|
+
notes: [
|
|
1125
|
+
response.status ? `Failed to fetch URL (HTTP ${response.status})` : "Failed to fetch URL",
|
|
1126
|
+
...(response.error ? [`Cause: ${response.error}`] : []),
|
|
1127
|
+
],
|
|
1109
1128
|
};
|
|
1110
1129
|
}
|
|
1111
1130
|
|
|
1112
1131
|
const { finalUrl, content: rawContent } = response;
|
|
1132
|
+
if (response.truncated) {
|
|
1133
|
+
notes.push(`Response body exceeded ${formatBytes(MAX_BYTES)} and was cut mid-stream; content is incomplete`);
|
|
1134
|
+
}
|
|
1113
1135
|
const mime = normalizeMime(response.contentType);
|
|
1114
1136
|
const extHint = getExtensionHint(finalUrl);
|
|
1115
1137
|
|
|
@@ -1276,6 +1298,7 @@ async function renderUrl(
|
|
|
1276
1298
|
mime,
|
|
1277
1299
|
extHint,
|
|
1278
1300
|
rawContent,
|
|
1301
|
+
response.bodySkipped === true,
|
|
1279
1302
|
timeout,
|
|
1280
1303
|
signal,
|
|
1281
1304
|
fetchedAt,
|
|
@@ -1321,7 +1344,7 @@ async function renderUrl(
|
|
|
1321
1344
|
}
|
|
1322
1345
|
|
|
1323
1346
|
if (isFeed || (isXml && (rawContent.includes("<rss") || rawContent.includes("<feed")))) {
|
|
1324
|
-
const parsed = parseFeedToMarkdown(rawContent);
|
|
1347
|
+
const parsed = await parseFeedToMarkdown(rawContent);
|
|
1325
1348
|
const output = finalizeOutput(parsed);
|
|
1326
1349
|
return {
|
|
1327
1350
|
url,
|
|
@@ -1414,7 +1437,7 @@ async function renderUrl(
|
|
|
1414
1437
|
const altResult = await loadPage(resolved, { timeout, signal });
|
|
1415
1438
|
if (altResult.ok && altResult.content.trim().length > 200) {
|
|
1416
1439
|
notes.push(`Used feed alternate: ${resolved}`);
|
|
1417
|
-
const parsed = parseFeedToMarkdown(altResult.content);
|
|
1440
|
+
const parsed = await parseFeedToMarkdown(altResult.content);
|
|
1418
1441
|
const output = finalizeOutput(parsed);
|
|
1419
1442
|
return {
|
|
1420
1443
|
url,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* number, all auth_keys) because the upside of staleness elimination
|
|
18
18
|
* dwarfs the cost of one cache miss.
|
|
19
19
|
*/
|
|
20
|
-
import { invalidateAllForNumber } from "./github-cache";
|
|
20
|
+
import { invalidateAllForNumber, invalidateAllForRepo } from "./github-cache";
|
|
21
21
|
|
|
22
22
|
const PR_URL_PATTERN = /^https:\/\/github\.com\/([^/\s]+\/[^/\s]+)\/pull\/(\d+)(?:[/?#].*)?$/i;
|
|
23
23
|
const ISSUE_URL_PATTERN = /^https:\/\/github\.com\/([^/\s]+\/[^/\s]+)\/issues\/(\d+)(?:[/?#].*)?$/i;
|
|
@@ -48,13 +48,60 @@ const MUTATING_PR_SUBCMDS: Record<string, true> = {
|
|
|
48
48
|
lock: true,
|
|
49
49
|
unlock: true,
|
|
50
50
|
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Flags whose value is the next argv token (`--milestone 3`). The detector
|
|
54
|
+
* must skip those values so `gh pr edit --milestone 3 14` invalidates #14,
|
|
55
|
+
* not #3. Curated for the mutating issue/PR subcommands above; a few short
|
|
56
|
+
* flags are booleans for *some* subcommands (e.g. `-c` is `--comment` text
|
|
57
|
+
* for `pr close` but a boolean for `pr review`) — we bias toward value-taking
|
|
58
|
+
* because over-skipping at worst falls back to repo-wide invalidation, while
|
|
59
|
+
* under-skipping invalidates the wrong number.
|
|
60
|
+
*/
|
|
61
|
+
const VALUE_TAKING_FLAGS: ReadonlySet<string> = new Set([
|
|
62
|
+
"-m",
|
|
63
|
+
"--milestone",
|
|
64
|
+
"-t",
|
|
65
|
+
"--title",
|
|
66
|
+
"-b",
|
|
67
|
+
"--body",
|
|
68
|
+
"-F",
|
|
69
|
+
"--body-file",
|
|
70
|
+
"-a",
|
|
71
|
+
"--assignee",
|
|
72
|
+
"--add-assignee",
|
|
73
|
+
"--remove-assignee",
|
|
74
|
+
"-l",
|
|
75
|
+
"--label",
|
|
76
|
+
"--add-label",
|
|
77
|
+
"--remove-label",
|
|
78
|
+
"-p",
|
|
79
|
+
"--project",
|
|
80
|
+
"--add-project",
|
|
81
|
+
"--remove-project",
|
|
82
|
+
"--add-reviewer",
|
|
83
|
+
"--remove-reviewer",
|
|
84
|
+
"-B",
|
|
85
|
+
"--base",
|
|
86
|
+
"-c",
|
|
87
|
+
"--comment",
|
|
88
|
+
"-r",
|
|
89
|
+
"--reason",
|
|
90
|
+
"--branch",
|
|
91
|
+
"--subject",
|
|
92
|
+
"--match-head-commit",
|
|
93
|
+
"--author-email",
|
|
94
|
+
]);
|
|
51
95
|
/**
|
|
52
96
|
* Walk a single shell command's token stream looking for a top-level
|
|
53
|
-
* `gh (issue|pr) <subcmd> <id-or-url
|
|
54
|
-
* invalidation key when one is found.
|
|
55
|
-
*
|
|
97
|
+
* `gh (issue|pr) <subcmd> [<id-or-url>]` invocation and return the
|
|
98
|
+
* invalidation key when one is found. `number === undefined` means the
|
|
99
|
+
* subcommand mutates state but names no identifier (gh defaults to the
|
|
100
|
+
* current branch's PR), so the caller must fall back to repo-wide
|
|
101
|
+
* invalidation. Returns `null` for non-matching commands so the caller can
|
|
102
|
+
* iterate cheaply.
|
|
56
103
|
*/
|
|
57
|
-
function detectGhMutation(tokens: readonly string[]): { number
|
|
104
|
+
function detectGhMutation(tokens: readonly string[]): { number?: number; repo?: string } | null {
|
|
58
105
|
const ghIdx = tokens.indexOf("gh");
|
|
59
106
|
if (ghIdx === -1) return null;
|
|
60
107
|
const subject = tokens[ghIdx + 1];
|
|
@@ -82,7 +129,9 @@ function detectGhMutation(tokens: readonly string[]): { number: number; repo?: s
|
|
|
82
129
|
}
|
|
83
130
|
for (let i = ghIdx + 3; i < tokens.length; i++) {
|
|
84
131
|
const token = tokens[i];
|
|
85
|
-
if (token === "-R" || token === "--repo") {
|
|
132
|
+
if (token === "-R" || token === "--repo" || VALUE_TAKING_FLAGS.has(token)) {
|
|
133
|
+
// Skip the flag's value so it is never mistaken for the positional
|
|
134
|
+
// identifier (`--milestone 3 14` must invalidate #14, not #3).
|
|
86
135
|
i++;
|
|
87
136
|
continue;
|
|
88
137
|
}
|
|
@@ -100,7 +149,9 @@ function detectGhMutation(tokens: readonly string[]): { number: number; repo?: s
|
|
|
100
149
|
}
|
|
101
150
|
}
|
|
102
151
|
}
|
|
103
|
-
|
|
152
|
+
// Mutating subcommand with no identifier: gh operates on the current
|
|
153
|
+
// branch's PR, which we cannot resolve synchronously here.
|
|
154
|
+
return repo !== undefined ? { repo } : {};
|
|
104
155
|
}
|
|
105
156
|
|
|
106
157
|
/**
|
|
@@ -195,6 +246,10 @@ export function invalidateGithubCacheForBashCommand(command: string): void {
|
|
|
195
246
|
for (const segment of segments) {
|
|
196
247
|
const hit = detectGhMutation(segment);
|
|
197
248
|
if (!hit) continue;
|
|
198
|
-
|
|
249
|
+
if (hit.number !== undefined) {
|
|
250
|
+
invalidateAllForNumber(hit.number, hit.repo);
|
|
251
|
+
} else {
|
|
252
|
+
invalidateAllForRepo(hit.repo);
|
|
253
|
+
}
|
|
199
254
|
}
|
|
200
255
|
}
|
package/src/tools/gh-renderer.ts
CHANGED
|
@@ -163,7 +163,7 @@ function getJobStateVisual(
|
|
|
163
163
|
): { iconRaw: string; iconColor: ToolUIColor; textColor: ThemeColor } {
|
|
164
164
|
if (job.conclusion && SUCCESS_CONCLUSIONS.has(job.conclusion)) {
|
|
165
165
|
return {
|
|
166
|
-
iconRaw: theme.
|
|
166
|
+
iconRaw: theme.status.success,
|
|
167
167
|
iconColor: "accent",
|
|
168
168
|
textColor: "success",
|
|
169
169
|
};
|