@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
package/src/tools/gh.ts
CHANGED
|
@@ -17,7 +17,7 @@ import githubDescription from "../prompts/tools/github.md" with { type: "text" }
|
|
|
17
17
|
import * as git from "../utils/git";
|
|
18
18
|
import type { ToolSession } from ".";
|
|
19
19
|
import { formatShortSha } from "./gh-format";
|
|
20
|
-
import { type CacheStatus, getOrFetchView, resolveGithubCacheAuthKey } from "./github-cache";
|
|
20
|
+
import { type CacheStatus, getOrFetchView, invalidateAllForNumber, resolveGithubCacheAuthKey } from "./github-cache";
|
|
21
21
|
import type { OutputMeta } from "./output-meta";
|
|
22
22
|
import { ToolError, throwIfAborted } from "./tool-errors";
|
|
23
23
|
import { toolResult } from "./tool-result";
|
|
@@ -192,6 +192,10 @@ const SEARCH_LIMIT_DEFAULT = 10;
|
|
|
192
192
|
const SEARCH_LIMIT_MAX = 50;
|
|
193
193
|
const FILE_PREVIEW_LIMIT = 50;
|
|
194
194
|
const RUN_WATCH_INTERVAL_DEFAULT = 3;
|
|
195
|
+
const RUN_WATCH_INTERVAL_SLOW = 15;
|
|
196
|
+
const RUN_WATCH_FAST_WINDOW_MS = 60_000;
|
|
197
|
+
const RUN_WATCH_NO_RUNS_GIVE_UP_MS = 90_000;
|
|
198
|
+
const RUN_WATCH_MAX_POLL_FAILURES = 5;
|
|
195
199
|
const RUN_WATCH_GRACE_DEFAULT = 5;
|
|
196
200
|
const RUN_WATCH_TAIL_DEFAULT = 15;
|
|
197
201
|
const RUN_WATCH_TAIL_MAX = 200;
|
|
@@ -716,7 +720,9 @@ export function parseSearchDateBound(raw: string, now: Date = new Date()): strin
|
|
|
716
720
|
|
|
717
721
|
const parsedMs = Date.parse(trimmed);
|
|
718
722
|
if (!Number.isNaN(parsedMs)) {
|
|
719
|
-
|
|
723
|
+
// GitHub search qualifiers accept seconds precision only
|
|
724
|
+
// (`YYYY-MM-DDTHH:MM:SSZ`); strip the milliseconds toISOString emits.
|
|
725
|
+
return new Date(parsedMs).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
720
726
|
}
|
|
721
727
|
|
|
722
728
|
throw new ToolError(
|
|
@@ -1277,6 +1283,16 @@ function isFailedJob(job: GhRunJobSnapshot): boolean {
|
|
|
1277
1283
|
return job.conclusion !== undefined && JOB_FAILURE_CONCLUSIONS.has(job.conclusion);
|
|
1278
1284
|
}
|
|
1279
1285
|
|
|
1286
|
+
const GH_RATE_LIMIT_ERROR_PATTERN = /rate limit|HTTP 429|abuse detection/i;
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Rate-limit / secondary-limit gh failures are transient; the run_watch poll
|
|
1290
|
+
* loops back off and retry them instead of discarding the whole watch.
|
|
1291
|
+
*/
|
|
1292
|
+
function isRateLimitedGhError(err: unknown): boolean {
|
|
1293
|
+
return err instanceof ToolError && GH_RATE_LIMIT_ERROR_PATTERN.test(err.message);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1280
1296
|
function formatJobState(job: GhRunJobSnapshot): string {
|
|
1281
1297
|
return job.conclusion ?? job.status ?? "unknown";
|
|
1282
1298
|
}
|
|
@@ -1800,6 +1816,7 @@ async function fetchRunsForCommit(
|
|
|
1800
1816
|
repo: string,
|
|
1801
1817
|
headSha: string,
|
|
1802
1818
|
signal?: AbortSignal,
|
|
1819
|
+
completedRunJobsCache?: Map<number, GhRunJobSnapshot[]>,
|
|
1803
1820
|
): Promise<GhRunSnapshot[]> {
|
|
1804
1821
|
// Filter only by `head_sha`. The SHA uniquely identifies the commit, so
|
|
1805
1822
|
// adding the GitHub `branch=` filter would wrongly exclude workflow runs
|
|
@@ -1826,7 +1843,19 @@ async function fetchRunsForCommit(
|
|
|
1826
1843
|
(response.workflow_runs ?? [])
|
|
1827
1844
|
.filter((run): run is GhActionsRunApi & { id: number } => typeof run.id === "number")
|
|
1828
1845
|
.map(async run => {
|
|
1829
|
-
|
|
1846
|
+
// Completed runs' job lists are stable until a re-run flips
|
|
1847
|
+
// `status` off "completed"; reuse them across watch polls so a
|
|
1848
|
+
// long watch does not refetch every finished run's jobs. A run
|
|
1849
|
+
// observed non-completed evicts its entry — when the re-run
|
|
1850
|
+
// completes, `status` flips back to "completed" and a stale
|
|
1851
|
+
// entry would serve the FIRST attempt's jobs and logs forever.
|
|
1852
|
+
const completed = run.status === "completed";
|
|
1853
|
+
if (!completed) completedRunJobsCache?.delete(run.id);
|
|
1854
|
+
let jobs = completed ? completedRunJobsCache?.get(run.id) : undefined;
|
|
1855
|
+
if (!jobs) {
|
|
1856
|
+
jobs = await fetchRunJobs(cwd, repo, run.id, signal);
|
|
1857
|
+
if (completed) completedRunJobsCache?.set(run.id, jobs);
|
|
1858
|
+
}
|
|
1830
1859
|
return normalizeRunSnapshot(run, jobs);
|
|
1831
1860
|
}),
|
|
1832
1861
|
);
|
|
@@ -1857,12 +1886,13 @@ async function fetchRunJobs(
|
|
|
1857
1886
|
signal,
|
|
1858
1887
|
{ repoProvided: true },
|
|
1859
1888
|
);
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
.filter((job): job is GhRunJobSnapshot => job !== null);
|
|
1889
|
+
const rawPage = response.jobs ?? [];
|
|
1890
|
+
const pageJobs = rawPage.map(job => normalizeRunJob(job)).filter((job): job is GhRunJobSnapshot => job !== null);
|
|
1863
1891
|
jobs.push(...pageJobs);
|
|
1864
1892
|
|
|
1865
|
-
|
|
1893
|
+
// Compare the raw page length: normalizeRunJob drops malformed items,
|
|
1894
|
+
// and a post-filter short page must not end pagination early.
|
|
1895
|
+
if (rawPage.length < RUN_JOBS_PAGE_SIZE) {
|
|
1866
1896
|
break;
|
|
1867
1897
|
}
|
|
1868
1898
|
|
|
@@ -1907,7 +1937,9 @@ async function fetchPrReviewComments(
|
|
|
1907
1937
|
.filter((comment): comment is GhPrReviewComment => comment !== null);
|
|
1908
1938
|
reviewComments.push(...pageComments);
|
|
1909
1939
|
|
|
1910
|
-
|
|
1940
|
+
// Compare the raw page length: a dropped malformed item must not end
|
|
1941
|
+
// pagination early and silently lose the remaining pages.
|
|
1942
|
+
if (response.length < REVIEW_COMMENTS_PAGE_SIZE) {
|
|
1911
1943
|
break;
|
|
1912
1944
|
}
|
|
1913
1945
|
|
|
@@ -2548,6 +2580,9 @@ async function fetchPrViewFresh(
|
|
|
2548
2580
|
*/
|
|
2549
2581
|
export async function getOrFetchIssue(options: IssueViewLookupOptions): Promise<ViewLookupResult<GhIssueViewData>> {
|
|
2550
2582
|
const identifier = requireNonEmpty(options.issue, "issue");
|
|
2583
|
+
if (identifier.startsWith("-")) {
|
|
2584
|
+
throw new ToolError(`invalid issue identifier: ${identifier}. Pass an issue number or URL.`);
|
|
2585
|
+
}
|
|
2551
2586
|
const includeComments = options.includeComments ?? true;
|
|
2552
2587
|
const authKey = options.cacheAuthKey === undefined ? (resolveGithubCacheAuthKey() ?? null) : options.cacheAuthKey;
|
|
2553
2588
|
const urlParse = parseIssueUrl(identifier);
|
|
@@ -2885,7 +2920,10 @@ async function fetchPrDiffFresh(
|
|
|
2885
2920
|
appendRepoFlag(args, repo, String(number));
|
|
2886
2921
|
const text = await git.github.text(cwd, args, signal, { repoProvided: true, trimOutput: false });
|
|
2887
2922
|
const payload = parsePrUnifiedDiff(text);
|
|
2888
|
-
|
|
2923
|
+
// `rendered` already carries the verbatim diff; blank the payload copy so
|
|
2924
|
+
// the cache row stores a potentially huge diff once instead of twice.
|
|
2925
|
+
// `getOrFetchPrDiff` rehydrates `unified` from `rendered`.
|
|
2926
|
+
return { rendered: text, sourceUrl: undefined, payload: { unified: "", files: payload.files } };
|
|
2889
2927
|
}
|
|
2890
2928
|
|
|
2891
2929
|
/**
|
|
@@ -2909,7 +2947,8 @@ export async function getOrFetchPrDiff(options: PrDiffLookupOptions): Promise<Vi
|
|
|
2909
2947
|
return {
|
|
2910
2948
|
rendered: lookup.rendered,
|
|
2911
2949
|
sourceUrl: lookup.sourceUrl,
|
|
2912
|
-
|
|
2950
|
+
// Rehydrate the unified text from `rendered` (stored once per row).
|
|
2951
|
+
payload: { unified: lookup.rendered, files: lookup.payload.files },
|
|
2913
2952
|
status: lookup.status,
|
|
2914
2953
|
fetchedAt: lookup.fetchedAt,
|
|
2915
2954
|
};
|
|
@@ -2930,9 +2969,35 @@ async function executePrCheckout(
|
|
|
2930
2969
|
const prRefs = prList.length > 0 ? prList : [undefined];
|
|
2931
2970
|
const isMulti = prRefs.length > 1;
|
|
2932
2971
|
|
|
2933
|
-
const
|
|
2972
|
+
const settled = await Promise.allSettled(
|
|
2934
2973
|
prRefs.map(prRef => checkoutPullRequest(session, signal, { prRef, repo, force })),
|
|
2935
2974
|
);
|
|
2975
|
+
const outcomes: PrCheckoutOutcome[] = [];
|
|
2976
|
+
const failures: Array<{ prRef: string | undefined; reason: unknown }> = [];
|
|
2977
|
+
for (let i = 0; i < settled.length; i++) {
|
|
2978
|
+
const entry = settled[i];
|
|
2979
|
+
if (entry.status === "fulfilled") outcomes.push(entry.value);
|
|
2980
|
+
else failures.push({ prRef: prRefs[i], reason: entry.reason });
|
|
2981
|
+
}
|
|
2982
|
+
if (failures.length > 0) {
|
|
2983
|
+
throwIfAborted(signal);
|
|
2984
|
+
const failureLines = failures.map(
|
|
2985
|
+
f => `- ${f.prRef ?? "(current branch)"}: ${f.reason instanceof Error ? f.reason.message : String(f.reason)}`,
|
|
2986
|
+
);
|
|
2987
|
+
if (outcomes.length === 0) {
|
|
2988
|
+
if (failures.length === 1) throw failures[0].reason;
|
|
2989
|
+
throw new ToolError(`all ${failures.length} PR checkouts failed:\n${failureLines.join("\n")}`);
|
|
2990
|
+
}
|
|
2991
|
+
// Partial success: report the worktrees that did get created alongside
|
|
2992
|
+
// the failures so the agent does not lose track of them.
|
|
2993
|
+
const sections = outcomes.map(formatPrCheckoutResult);
|
|
2994
|
+
const header = `# ${outcomes.length}/${settled.length} Pull Request Worktrees checked out (${failures.length} failed)`;
|
|
2995
|
+
const text = [header, "", ...joinSections(sections), "", "## Failed", ...failureLines].join("\n").trim();
|
|
2996
|
+
return buildTextResult(text, undefined, {
|
|
2997
|
+
repo,
|
|
2998
|
+
checkouts: outcomes.map(outcomeToSummary),
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
2936
3001
|
|
|
2937
3002
|
if (!isMulti) {
|
|
2938
3003
|
const [outcome] = outcomes;
|
|
@@ -2983,6 +3048,9 @@ async function checkoutPullRequest(
|
|
|
2983
3048
|
options: PrCheckoutOptions,
|
|
2984
3049
|
): Promise<PrCheckoutOutcome> {
|
|
2985
3050
|
const { prRef, repo, force } = options;
|
|
3051
|
+
if (prRef?.startsWith("-")) {
|
|
3052
|
+
throw new ToolError(`invalid PR identifier: ${prRef}. Pass a PR number, URL, or branch name.`);
|
|
3053
|
+
}
|
|
2986
3054
|
const args = ["pr", "view"];
|
|
2987
3055
|
if (prRef) args.push(prRef);
|
|
2988
3056
|
appendRepoFlag(args, repo, prRef);
|
|
@@ -3122,6 +3190,14 @@ async function executePrPush(
|
|
|
3122
3190
|
signal,
|
|
3123
3191
|
});
|
|
3124
3192
|
|
|
3193
|
+
// A successful push changes what `pr://N` and `pr://N/diff` should show;
|
|
3194
|
+
// drop the cached rows so the canonical "push → re-read diff" flow sees
|
|
3195
|
+
// fresh data instead of a soft-TTL stale snapshot.
|
|
3196
|
+
const pushedPr = parsePullRequestUrl(target.prUrl);
|
|
3197
|
+
if (pushedPr.prNumber !== undefined) {
|
|
3198
|
+
invalidateAllForNumber(pushedPr.prNumber, pushedPr.repo);
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3125
3201
|
return buildTextResult(
|
|
3126
3202
|
formatPrPushResult({
|
|
3127
3203
|
localBranch,
|
|
@@ -3376,9 +3452,24 @@ async function executeRunWatch(
|
|
|
3376
3452
|
const explicitRepo = normalizeOptionalString(params.repo);
|
|
3377
3453
|
const runReference = parseRunReference(params.run);
|
|
3378
3454
|
const repo = await resolveGitHubRepo(session.cwd, explicitRepo, runReference.repo, signal);
|
|
3379
|
-
const intervalSeconds = RUN_WATCH_INTERVAL_DEFAULT;
|
|
3380
3455
|
const graceSeconds = RUN_WATCH_GRACE_DEFAULT;
|
|
3381
3456
|
const tail = resolveTailLimit(params.tail);
|
|
3457
|
+
const watchStartMs = Date.now();
|
|
3458
|
+
// Fast polls for the first minute for snappy feedback, then back off:
|
|
3459
|
+
// every commit-watch poll is one runs-list call plus one jobs call per
|
|
3460
|
+
// non-completed run, and long builds must not burn the shared
|
|
3461
|
+
// authenticated REST quota.
|
|
3462
|
+
const currentIntervalSeconds = () =>
|
|
3463
|
+
Date.now() - watchStartMs < RUN_WATCH_FAST_WINDOW_MS ? RUN_WATCH_INTERVAL_DEFAULT : RUN_WATCH_INTERVAL_SLOW;
|
|
3464
|
+
let consecutivePollFailures = 0;
|
|
3465
|
+
const handlePollError = async (err: unknown): Promise<void> => {
|
|
3466
|
+
if (signal?.aborted) throw err;
|
|
3467
|
+
consecutivePollFailures += 1;
|
|
3468
|
+
if (!isRateLimitedGhError(err) || consecutivePollFailures > RUN_WATCH_MAX_POLL_FAILURES) throw err;
|
|
3469
|
+
// Rate-limited: back off with the slow interval and retry instead of
|
|
3470
|
+
// discarding the whole watch (and its accumulated context).
|
|
3471
|
+
await scheduler.wait(RUN_WATCH_INTERVAL_SLOW * 1000, { signal });
|
|
3472
|
+
};
|
|
3382
3473
|
if (runReference.runId !== undefined) {
|
|
3383
3474
|
const runId = runReference.runId;
|
|
3384
3475
|
let pollCount = 0;
|
|
@@ -3387,7 +3478,14 @@ async function executeRunWatch(
|
|
|
3387
3478
|
throwIfAborted(signal);
|
|
3388
3479
|
pollCount += 1;
|
|
3389
3480
|
|
|
3390
|
-
let run
|
|
3481
|
+
let run: GhRunSnapshot;
|
|
3482
|
+
try {
|
|
3483
|
+
run = await fetchRunSnapshot(session.cwd, repo, runId, signal);
|
|
3484
|
+
} catch (err) {
|
|
3485
|
+
await handlePollError(err);
|
|
3486
|
+
continue;
|
|
3487
|
+
}
|
|
3488
|
+
consecutivePollFailures = 0;
|
|
3391
3489
|
const details = buildRunWatchDetails(repo, run, {
|
|
3392
3490
|
state: "watching",
|
|
3393
3491
|
pollCount,
|
|
@@ -3397,7 +3495,7 @@ async function executeRunWatch(
|
|
|
3397
3495
|
details,
|
|
3398
3496
|
});
|
|
3399
3497
|
|
|
3400
|
-
|
|
3498
|
+
let failedJobs = run.jobs.filter(isFailedJob);
|
|
3401
3499
|
const runCompleted = run.status === "completed";
|
|
3402
3500
|
|
|
3403
3501
|
if (failedJobs.length > 0) {
|
|
@@ -3417,13 +3515,28 @@ async function executeRunWatch(
|
|
|
3417
3515
|
}),
|
|
3418
3516
|
});
|
|
3419
3517
|
await scheduler.wait(graceSeconds * 1000, { signal });
|
|
3420
|
-
|
|
3518
|
+
try {
|
|
3519
|
+
const refetched = await fetchRunSnapshot(session.cwd, repo, runId, signal);
|
|
3520
|
+
const refetchedFailed = refetched.jobs.filter(isFailedJob);
|
|
3521
|
+
// An auto-retry can reset job conclusions between
|
|
3522
|
+
// detection and refetch; keep the originally-detected
|
|
3523
|
+
// failure list (and its snapshot) when the refetch no
|
|
3524
|
+
// longer shows any failures so the watch never ends
|
|
3525
|
+
// with a failure result and zero logs.
|
|
3526
|
+
if (refetchedFailed.length > 0) {
|
|
3527
|
+
run = refetched;
|
|
3528
|
+
failedJobs = refetchedFailed;
|
|
3529
|
+
}
|
|
3530
|
+
} catch (err) {
|
|
3531
|
+
if (signal?.aborted) throw err;
|
|
3532
|
+
// Refetch failure: report from the original snapshot.
|
|
3533
|
+
}
|
|
3421
3534
|
}
|
|
3422
3535
|
|
|
3423
3536
|
const failedJobLogs = await fetchFailedJobLogs(
|
|
3424
3537
|
session.cwd,
|
|
3425
3538
|
repo,
|
|
3426
|
-
|
|
3539
|
+
failedJobs.map(job => ({ run, job })),
|
|
3427
3540
|
tail,
|
|
3428
3541
|
signal,
|
|
3429
3542
|
);
|
|
@@ -3451,7 +3564,7 @@ async function executeRunWatch(
|
|
|
3451
3564
|
return buildTextResult(formatRunWatchResult(repo, run, [], tail), run.url, finalDetails);
|
|
3452
3565
|
}
|
|
3453
3566
|
|
|
3454
|
-
await scheduler.wait(
|
|
3567
|
+
await scheduler.wait(currentIntervalSeconds() * 1000, { signal });
|
|
3455
3568
|
}
|
|
3456
3569
|
}
|
|
3457
3570
|
|
|
@@ -3479,12 +3592,22 @@ async function executeRunWatch(
|
|
|
3479
3592
|
}
|
|
3480
3593
|
let pollCount = 0;
|
|
3481
3594
|
let settledSuccessSignature: string | undefined;
|
|
3595
|
+
let everSawRuns = false;
|
|
3596
|
+
const completedRunJobsCache = new Map<number, GhRunJobSnapshot[]>();
|
|
3482
3597
|
|
|
3483
3598
|
while (true) {
|
|
3484
3599
|
throwIfAborted(signal);
|
|
3485
3600
|
pollCount += 1;
|
|
3486
3601
|
|
|
3487
|
-
let runs
|
|
3602
|
+
let runs: GhRunSnapshot[];
|
|
3603
|
+
try {
|
|
3604
|
+
runs = await fetchRunsForCommit(session.cwd, repo, headSha, signal, completedRunJobsCache);
|
|
3605
|
+
} catch (err) {
|
|
3606
|
+
await handlePollError(err);
|
|
3607
|
+
continue;
|
|
3608
|
+
}
|
|
3609
|
+
consecutivePollFailures = 0;
|
|
3610
|
+
if (runs.length > 0) everSawRuns = true;
|
|
3488
3611
|
const details = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
3489
3612
|
state: "watching",
|
|
3490
3613
|
pollCount,
|
|
@@ -3496,6 +3619,7 @@ async function executeRunWatch(
|
|
|
3496
3619
|
|
|
3497
3620
|
const outcome = getRunCollectionOutcome(runs);
|
|
3498
3621
|
if (outcome === "failure") {
|
|
3622
|
+
let failedPairs = runs.flatMap(run => run.jobs.filter(isFailedJob).map(job => ({ run, job })));
|
|
3499
3623
|
if (graceSeconds > 0) {
|
|
3500
3624
|
const note = `Failure detected. Waiting ${graceSeconds}s to capture concurrent failures before fetching logs.`;
|
|
3501
3625
|
onUpdate?.({
|
|
@@ -3512,16 +3636,23 @@ async function executeRunWatch(
|
|
|
3512
3636
|
}),
|
|
3513
3637
|
});
|
|
3514
3638
|
await scheduler.wait(graceSeconds * 1000, { signal });
|
|
3515
|
-
|
|
3639
|
+
try {
|
|
3640
|
+
const refetched = await fetchRunsForCommit(session.cwd, repo, headSha, signal, completedRunJobsCache);
|
|
3641
|
+
const refetchedPairs = refetched.flatMap(run => run.jobs.filter(isFailedJob).map(job => ({ run, job })));
|
|
3642
|
+
// Keep the originally-detected failure list when an
|
|
3643
|
+
// auto-retry reset the conclusions during the grace window
|
|
3644
|
+
// (see the run-id branch above).
|
|
3645
|
+
if (refetchedPairs.length > 0) {
|
|
3646
|
+
runs = refetched;
|
|
3647
|
+
failedPairs = refetchedPairs;
|
|
3648
|
+
}
|
|
3649
|
+
} catch (err) {
|
|
3650
|
+
if (signal?.aborted) throw err;
|
|
3651
|
+
// Refetch failure: report from the original snapshots.
|
|
3652
|
+
}
|
|
3516
3653
|
}
|
|
3517
3654
|
|
|
3518
|
-
const failedJobLogs = await fetchFailedJobLogs(
|
|
3519
|
-
session.cwd,
|
|
3520
|
-
repo,
|
|
3521
|
-
runs.flatMap(run => run.jobs.filter(isFailedJob).map(job => ({ run, job }))),
|
|
3522
|
-
tail,
|
|
3523
|
-
signal,
|
|
3524
|
-
);
|
|
3655
|
+
const failedJobLogs = await fetchFailedJobLogs(session.cwd, repo, failedPairs, tail, signal);
|
|
3525
3656
|
const finalDetails = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
|
|
3526
3657
|
state: "completed",
|
|
3527
3658
|
failedJobLogs,
|
|
@@ -3553,7 +3684,8 @@ async function executeRunWatch(
|
|
|
3553
3684
|
}
|
|
3554
3685
|
|
|
3555
3686
|
settledSuccessSignature = signature;
|
|
3556
|
-
const
|
|
3687
|
+
const confirmWaitSeconds = currentIntervalSeconds();
|
|
3688
|
+
const note = `All known workflow runs completed successfully. Waiting ${confirmWaitSeconds}s to ensure no additional runs appear for this commit.`;
|
|
3557
3689
|
onUpdate?.({
|
|
3558
3690
|
content: [
|
|
3559
3691
|
{
|
|
@@ -3567,11 +3699,22 @@ async function executeRunWatch(
|
|
|
3567
3699
|
note,
|
|
3568
3700
|
}),
|
|
3569
3701
|
});
|
|
3570
|
-
await scheduler.wait(
|
|
3702
|
+
await scheduler.wait(confirmWaitSeconds * 1000, { signal });
|
|
3571
3703
|
continue;
|
|
3572
3704
|
}
|
|
3573
3705
|
|
|
3574
3706
|
settledSuccessSignature = undefined;
|
|
3575
|
-
|
|
3707
|
+
if (!everSawRuns && Date.now() - watchStartMs >= RUN_WATCH_NO_RUNS_GIVE_UP_MS) {
|
|
3708
|
+
// A repo with no Actions configured (or Actions disabled) never
|
|
3709
|
+
// produces a run for this commit; give up with a clear message
|
|
3710
|
+
// instead of polling forever.
|
|
3711
|
+
const elapsedSec = Math.round((Date.now() - watchStartMs) / 1000);
|
|
3712
|
+
return buildTextResult(
|
|
3713
|
+
`No workflow runs found for ${repo}@${formatShortSha(headSha) ?? headSha} after ${elapsedSec}s (${pollCount} polls). The commit may not trigger any GitHub Actions workflows, or Actions may be disabled for this repository. Pass \`run\` to watch a specific run.`,
|
|
3714
|
+
undefined,
|
|
3715
|
+
buildCommitRunWatchDetails(repo, headSha, branch, runs, { state: "completed", pollCount }),
|
|
3716
|
+
);
|
|
3717
|
+
}
|
|
3718
|
+
await scheduler.wait(currentIntervalSeconds() * 1000, { signal });
|
|
3576
3719
|
}
|
|
3577
3720
|
}
|
|
@@ -174,6 +174,21 @@ function hashCacheIdentity(parts: string[]): string {
|
|
|
174
174
|
return Bun.hash(parts.map(part => `${part.length}:${part}`).join("|")).toString(36);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Memo for {@link resolveGithubCacheAuthKey}. Recomputed only when the token
|
|
179
|
+
* env vars or the hosts.yml path/mtime change, so the per-lookup cost on the
|
|
180
|
+
* cache hot path is four env reads plus one `stat` instead of a full file
|
|
181
|
+
* read + hash.
|
|
182
|
+
*/
|
|
183
|
+
interface AuthKeyMemoEntry {
|
|
184
|
+
envSig: string;
|
|
185
|
+
hostsPath: string;
|
|
186
|
+
hostsMtimeMs: number;
|
|
187
|
+
value: string | undefined;
|
|
188
|
+
}
|
|
189
|
+
const AUTH_KEY_TOKEN_ENV_VARS = ["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN"];
|
|
190
|
+
const authKeyMemo = new Map<string, AuthKeyMemoEntry>();
|
|
191
|
+
|
|
177
192
|
/**
|
|
178
193
|
* Best-effort local fingerprint for the active GitHub CLI credentials.
|
|
179
194
|
*
|
|
@@ -185,16 +200,32 @@ function hashCacheIdentity(parts: string[]): string {
|
|
|
185
200
|
* credential source is visible, callers should pass `null` to bypass caching.
|
|
186
201
|
*/
|
|
187
202
|
export function resolveGithubCacheAuthKey(host: string = process.env.GH_HOST || "github.com"): string | undefined {
|
|
203
|
+
const hostsPath = path.join(getGhConfigDir(), "hosts.yml");
|
|
204
|
+
let envSig = "";
|
|
205
|
+
for (const name of AUTH_KEY_TOKEN_ENV_VARS) {
|
|
206
|
+
const value = process.env[name];
|
|
207
|
+
if (value) envSig += `${name}=${value.length}:${value}\0`;
|
|
208
|
+
}
|
|
209
|
+
let hostsMtimeMs = -1;
|
|
210
|
+
try {
|
|
211
|
+
hostsMtimeMs = fs.statSync(hostsPath, { throwIfNoEntry: false })?.mtimeMs ?? -1;
|
|
212
|
+
} catch (err) {
|
|
213
|
+
logger.debug("github cache: failed to stat gh hosts config for cache identity", { err: String(err) });
|
|
214
|
+
}
|
|
215
|
+
const memo = authKeyMemo.get(host);
|
|
216
|
+
if (memo && memo.envSig === envSig && memo.hostsPath === hostsPath && memo.hostsMtimeMs === hostsMtimeMs) {
|
|
217
|
+
return memo.value;
|
|
218
|
+
}
|
|
219
|
+
|
|
188
220
|
const parts: string[] = [`host:${host}`];
|
|
189
221
|
let hasCredentialMaterial = false;
|
|
190
|
-
for (const name of
|
|
222
|
+
for (const name of AUTH_KEY_TOKEN_ENV_VARS) {
|
|
191
223
|
const value = process.env[name];
|
|
192
224
|
if (!value) continue;
|
|
193
225
|
hasCredentialMaterial = true;
|
|
194
226
|
parts.push(`${name}:${value}`);
|
|
195
227
|
}
|
|
196
228
|
try {
|
|
197
|
-
const hostsPath = path.join(getGhConfigDir(), "hosts.yml");
|
|
198
229
|
const hosts = fs.readFileSync(hostsPath, "utf8");
|
|
199
230
|
hasCredentialMaterial = true;
|
|
200
231
|
parts.push(`hosts:${hosts}`);
|
|
@@ -203,8 +234,9 @@ export function resolveGithubCacheAuthKey(host: string = process.env.GH_HOST ||
|
|
|
203
234
|
logger.debug("github cache: failed to read gh hosts config for cache identity", { err: String(err) });
|
|
204
235
|
}
|
|
205
236
|
}
|
|
206
|
-
|
|
207
|
-
|
|
237
|
+
const value = hasCredentialMaterial ? `${host}:${hashCacheIdentity(parts)}` : undefined;
|
|
238
|
+
authKeyMemo.set(host, { envSig, hostsPath, hostsMtimeMs, value });
|
|
239
|
+
return value;
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
function normalizeRepo(repo: string): string {
|
|
@@ -352,6 +384,26 @@ export function clearAll(): void {
|
|
|
352
384
|
}
|
|
353
385
|
}
|
|
354
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Drop every cached row for a repo, or all rows when the repo is unknown.
|
|
389
|
+
* Fallback for current-branch `gh pr merge`/`gh pr close`-style mutations
|
|
390
|
+
* where the bash command names no PR number or URL, so the target row cannot
|
|
391
|
+
* be identified. Over-invalidation is deliberate (see module header).
|
|
392
|
+
*/
|
|
393
|
+
export function invalidateAllForRepo(repo?: string): void {
|
|
394
|
+
const db = openDb();
|
|
395
|
+
if (!db) return;
|
|
396
|
+
try {
|
|
397
|
+
if (repo === undefined) {
|
|
398
|
+
db.prepare("DELETE FROM github_view_cache").run();
|
|
399
|
+
} else {
|
|
400
|
+
db.prepare("DELETE FROM github_view_cache WHERE repo = ?").run(normalizeRepo(repo));
|
|
401
|
+
}
|
|
402
|
+
} catch (err) {
|
|
403
|
+
logger.debug("github cache: invalidateAllForRepo failed", { err: String(err) });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
355
407
|
/**
|
|
356
408
|
* Test/maintenance helper. Closes and forgets the cached connection so the
|
|
357
409
|
* next access reopens against (possibly) a different DB path.
|
|
@@ -367,6 +419,7 @@ export function resetForTests(): void {
|
|
|
367
419
|
cachedDb = null;
|
|
368
420
|
openAttempted = false;
|
|
369
421
|
lastSweepAt = 0;
|
|
422
|
+
authKeyMemo.clear();
|
|
370
423
|
}
|
|
371
424
|
|
|
372
425
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -467,6 +520,12 @@ function storeResult<T>(
|
|
|
467
520
|
});
|
|
468
521
|
}
|
|
469
522
|
|
|
523
|
+
/**
|
|
524
|
+
* In-flight background refreshes keyed by row identity. N concurrent stale
|
|
525
|
+
* reads of the same row must spawn one `gh` subprocess, not N identical ones.
|
|
526
|
+
*/
|
|
527
|
+
const inflightRefreshes = new Set<string>();
|
|
528
|
+
|
|
470
529
|
function scheduleBackgroundRefresh<T>(
|
|
471
530
|
authKey: string,
|
|
472
531
|
repo: string,
|
|
@@ -475,9 +534,11 @@ function scheduleBackgroundRefresh<T>(
|
|
|
475
534
|
includeComments: boolean,
|
|
476
535
|
fetchFresh: () => Promise<FreshResult<T>>,
|
|
477
536
|
): void {
|
|
537
|
+
const key = `${authKey}|${normalizeRepo(repo)}|${kind}|${number}|${includeComments ? 1 : 0}`;
|
|
538
|
+
if (inflightRefreshes.has(key)) return;
|
|
539
|
+
inflightRefreshes.add(key);
|
|
478
540
|
queueMicrotask(() => {
|
|
479
|
-
|
|
480
|
-
promise
|
|
541
|
+
fetchFresh()
|
|
481
542
|
.then(fresh => {
|
|
482
543
|
storeResult(authKey, repo, kind, number, includeComments, fresh, Date.now());
|
|
483
544
|
})
|
|
@@ -488,6 +549,9 @@ function scheduleBackgroundRefresh<T>(
|
|
|
488
549
|
kind,
|
|
489
550
|
number,
|
|
490
551
|
});
|
|
552
|
+
})
|
|
553
|
+
.finally(() => {
|
|
554
|
+
inflightRefreshes.delete(key);
|
|
491
555
|
});
|
|
492
556
|
});
|
|
493
557
|
}
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
type ApiKey,
|
|
5
|
-
type FetchImpl,
|
|
6
|
-
getAntigravityUserAgent,
|
|
7
|
-
getEnvApiKey,
|
|
8
|
-
type Model,
|
|
9
|
-
withAuth,
|
|
10
|
-
} from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { type ApiKey, type FetchImpl, getEnvApiKey, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
11
4
|
import {
|
|
12
5
|
CODEX_BASE_URL,
|
|
13
6
|
getCodexAccountId,
|
|
14
7
|
OPENAI_HEADER_VALUES,
|
|
15
8
|
OPENAI_HEADERS,
|
|
16
9
|
URL_PATHS,
|
|
17
|
-
} from "@oh-my-pi/pi-
|
|
10
|
+
} from "@oh-my-pi/pi-catalog/wire/codex";
|
|
11
|
+
import { getAntigravityUserAgent } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
|
|
18
12
|
import {
|
|
19
13
|
$env,
|
|
20
14
|
isEnoent,
|
|
@@ -478,8 +472,13 @@ function parseAntigravityCredentials(raw: string): ParsedAntigravityCredentials
|
|
|
478
472
|
return null;
|
|
479
473
|
}
|
|
480
474
|
|
|
481
|
-
async function findAntigravityCredentials(
|
|
482
|
-
|
|
475
|
+
async function findAntigravityCredentials(
|
|
476
|
+
modelRegistry: ModelRegistry,
|
|
477
|
+
sessionId?: string,
|
|
478
|
+
): Promise<ImageApiKey | null> {
|
|
479
|
+
const apiKey = await modelRegistry.getApiKeyForProvider("google-antigravity", sessionId, {
|
|
480
|
+
modelId: DEFAULT_ANTIGRAVITY_MODEL,
|
|
481
|
+
});
|
|
483
482
|
if (!apiKey) return null;
|
|
484
483
|
|
|
485
484
|
const parsed = parseAntigravityCredentials(apiKey);
|
|
@@ -529,7 +528,7 @@ async function findImageApiKey(
|
|
|
529
528
|
if (openAI) return openAI;
|
|
530
529
|
// Fall through to auto-detect if preferred provider key not found.
|
|
531
530
|
} else if (preferredImageProvider === "antigravity" && modelRegistry) {
|
|
532
|
-
const antigravity = await findAntigravityCredentials(modelRegistry);
|
|
531
|
+
const antigravity = await findAntigravityCredentials(modelRegistry, sessionId);
|
|
533
532
|
if (antigravity) return antigravity;
|
|
534
533
|
// Fall through to auto-detect if preferred provider key not found.
|
|
535
534
|
} else if (preferredImageProvider === "gemini") {
|
|
@@ -553,7 +552,7 @@ async function findImageApiKey(
|
|
|
553
552
|
if (openAI) return openAI;
|
|
554
553
|
|
|
555
554
|
if (modelRegistry) {
|
|
556
|
-
const antigravity = await findAntigravityCredentials(modelRegistry);
|
|
555
|
+
const antigravity = await findAntigravityCredentials(modelRegistry, sessionId);
|
|
557
556
|
if (antigravity) return antigravity;
|
|
558
557
|
}
|
|
559
558
|
|
|
@@ -1058,6 +1057,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1058
1057
|
const hostedKey: ApiKey = ctx.modelRegistry.resolver(hostedModel.provider, {
|
|
1059
1058
|
sessionId,
|
|
1060
1059
|
baseUrl: hostedModel.baseUrl,
|
|
1060
|
+
modelId: hostedModel.id,
|
|
1061
1061
|
});
|
|
1062
1062
|
|
|
1063
1063
|
const parsed = await withAuth(
|
|
@@ -1119,6 +1119,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1119
1119
|
const prompt = assemblePrompt(params);
|
|
1120
1120
|
const antigravityKey: ApiKey = ctx.modelRegistry.resolver("google-antigravity", {
|
|
1121
1121
|
sessionId,
|
|
1122
|
+
modelId: DEFAULT_ANTIGRAVITY_MODEL,
|
|
1122
1123
|
});
|
|
1123
1124
|
|
|
1124
1125
|
const response = await withAuth(
|
package/src/tools/index.ts
CHANGED
|
@@ -320,6 +320,13 @@ export interface ToolSession {
|
|
|
320
320
|
* model for each file. Lazily initialized by `getDiagnosticsLedger`. */
|
|
321
321
|
diagnosticsLedger?: import("../lsp/diagnostics-ledger").DiagnosticsLedger;
|
|
322
322
|
|
|
323
|
+
/** Per-session ledger of consecutive byte-identical no-op edits, keyed by
|
|
324
|
+
* canonical file path. The hashline executor escalates a soft no-op hint
|
|
325
|
+
* to a thrown error once the same payload no-ops `NOOP_HARD_LIMIT` times,
|
|
326
|
+
* breaking subagent loops that ignore the textual hint (issue #2081).
|
|
327
|
+
* Lazily initialized by `getNoopLoopGuard`. */
|
|
328
|
+
noopLoopGuard?: import("../edit/hashline/noop-loop-guard").NoopLoopGuard;
|
|
329
|
+
|
|
323
330
|
/** Queue a hidden message to be injected at the next agent turn. */
|
|
324
331
|
queueDeferredMessage?(message: CustomMessage): void;
|
|
325
332
|
/** Queue late LSP diagnostics (arrived after an edit/write returned) to be shown
|
|
@@ -463,7 +470,12 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
463
470
|
!allowJs &&
|
|
464
471
|
(requestedTools === undefined || requestedTools.includes("eval"))
|
|
465
472
|
) {
|
|
466
|
-
const availability = await logger.time(
|
|
473
|
+
const availability = await logger.time(
|
|
474
|
+
"createTools:pythonCheck",
|
|
475
|
+
checkPythonKernelAvailability,
|
|
476
|
+
session.cwd,
|
|
477
|
+
session.settings.get("python.interpreter")?.trim() || undefined,
|
|
478
|
+
);
|
|
467
479
|
pythonAvailable = availability.ok;
|
|
468
480
|
if (!availability.ok) {
|
|
469
481
|
logger.warn("Python kernel unavailable and JS backend disabled; eval will be unavailable", {
|
|
@@ -141,6 +141,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
141
141
|
apiKey: modelRegistry.resolver(model.provider, {
|
|
142
142
|
sessionId: this.session.getSessionId?.() ?? undefined,
|
|
143
143
|
baseUrl: model.baseUrl,
|
|
144
|
+
modelId: model.id,
|
|
144
145
|
}),
|
|
145
146
|
signal,
|
|
146
147
|
},
|
package/src/tools/irc.ts
CHANGED
|
@@ -244,11 +244,15 @@ function errorResult(text: string, details: IrcDetails): AgentToolResult<IrcDeta
|
|
|
244
244
|
return {
|
|
245
245
|
content: [{ type: "text", text }],
|
|
246
246
|
details,
|
|
247
|
+
isError: true,
|
|
247
248
|
};
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
function normalizeIrcTimeoutMs(value: number): number {
|
|
251
|
-
if (
|
|
252
|
+
if (value === 0) return 0; // 0 = timeout disabled
|
|
253
|
+
// Negative or non-finite settings are misconfigurations — fall back to the
|
|
254
|
+
// default instead of producing an instant 1 ms timeout.
|
|
255
|
+
if (!Number.isFinite(value) || value < 0) return DEFAULT_IRC_TIMEOUT_MS;
|
|
252
256
|
return Math.max(1, Math.trunc(value));
|
|
253
257
|
}
|
|
254
258
|
|
package/src/tools/job.ts
CHANGED
|
@@ -454,7 +454,7 @@ export const jobToolRenderer = {
|
|
|
454
454
|
|
|
455
455
|
let cached: RenderCache | undefined;
|
|
456
456
|
return {
|
|
457
|
-
render(width: number): string[] {
|
|
457
|
+
render(width: number): readonly string[] {
|
|
458
458
|
const expanded = options.expanded;
|
|
459
459
|
const spinnerFrame = options.spinnerFrame ?? 0;
|
|
460
460
|
const key = new Hasher().bool(expanded).u32(width).u32(spinnerFrame).digest();
|