@camerontaylor/paseo-server 0.8.0-fork.1
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/.env.example +31 -0
- package/README.md +111 -0
- package/dist/scripts/dev-runner.js +37 -0
- package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
- package/dist/scripts/supervisor-entrypoint.js +160 -0
- package/dist/scripts/supervisor-log-config.js +31 -0
- package/dist/scripts/supervisor.js +313 -0
- package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
- package/dist/server/executable-resolution/executable-resolution.js +121 -0
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +65 -0
- package/dist/server/server/agent/activity-curator.d.ts +40 -0
- package/dist/server/server/agent/activity-curator.js +243 -0
- package/dist/server/server/agent/agent-archive.d.ts +11 -0
- package/dist/server/server/agent/agent-archive.js +16 -0
- package/dist/server/server/agent/agent-configuration-validator.d.ts +18 -0
- package/dist/server/server/agent/agent-configuration-validator.js +42 -0
- package/dist/server/server/agent/agent-loading.d.ts +17 -0
- package/dist/server/server/agent/agent-loading.js +88 -0
- package/dist/server/server/agent/agent-manager.d.ts +555 -0
- package/dist/server/server/agent/agent-manager.js +3930 -0
- package/dist/server/server/agent/agent-owner.d.ts +12 -0
- package/dist/server/server/agent/agent-owner.js +12 -0
- package/dist/server/server/agent/agent-projections.d.ts +26 -0
- package/dist/server/server/agent/agent-projections.js +414 -0
- package/dist/server/server/agent/agent-prompt.d.ts +91 -0
- package/dist/server/server/agent/agent-prompt.js +359 -0
- package/dist/server/server/agent/agent-response-loop.d.ts +72 -0
- package/dist/server/server/agent/agent-response-loop.js +336 -0
- package/dist/server/server/agent/agent-run-state.d.ts +76 -0
- package/dist/server/server/agent/agent-run-state.js +212 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +742 -0
- package/dist/server/server/agent/agent-sdk-types.js +25 -0
- package/dist/server/server/agent/agent-storage.d.ts +148 -0
- package/dist/server/server/agent/agent-storage.js +401 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +43 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +187 -0
- package/dist/server/server/agent/agent-timeline-content.d.ts +6 -0
- package/dist/server/server/agent/agent-timeline-content.js +62 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +54 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +31 -0
- package/dist/server/server/agent/agent-timeline-store.js +249 -0
- package/dist/server/server/agent/audio-utils.d.ts +3 -0
- package/dist/server/server/agent/audio-utils.js +19 -0
- package/dist/server/server/agent/create-agent/create.d.ts +102 -0
- package/dist/server/server/agent/create-agent/create.js +362 -0
- package/dist/server/server/agent/create-agent/intent.d.ts +25 -0
- package/dist/server/server/agent/create-agent/intent.js +30 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +65 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +166 -0
- package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
- package/dist/server/server/agent/create-agent-mode.js +65 -0
- package/dist/server/server/agent/create-agent-title.d.ts +10 -0
- package/dist/server/server/agent/create-agent-title.js +34 -0
- package/dist/server/server/agent/dictation-debug.d.ts +13 -0
- package/dist/server/server/agent/dictation-debug.js +50 -0
- package/dist/server/server/agent/import-sessions.d.ts +57 -0
- package/dist/server/server/agent/import-sessions.js +240 -0
- package/dist/server/server/agent/lifecycle-command.d.ts +73 -0
- package/dist/server/server/agent/lifecycle-command.js +99 -0
- package/dist/server/server/agent/mcp-server.d.ts +5 -0
- package/dist/server/server/agent/mcp-server.js +31 -0
- package/dist/server/server/agent/mcp-shared.d.ts +181 -0
- package/dist/server/server/agent/mcp-shared.js +199 -0
- package/dist/server/server/agent/mutable-provider-config-owner.d.ts +8 -0
- package/dist/server/server/agent/mutable-provider-config-owner.js +35 -0
- package/dist/server/server/agent/paseo-tool-policy.d.ts +9 -0
- package/dist/server/server/agent/paseo-tool-policy.js +16 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
- package/dist/server/server/agent/pcm16-resampler.js +63 -0
- package/dist/server/server/agent/permission-response.d.ts +15 -0
- package/dist/server/server/agent/permission-response.js +14 -0
- package/dist/server/server/agent/plugin-provider.d.ts +16 -0
- package/dist/server/server/agent/plugin-provider.js +1356 -0
- package/dist/server/server/agent/prompt-attachments.d.ts +12 -0
- package/dist/server/server/agent/prompt-attachments.js +182 -0
- package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
- package/dist/server/server/agent/provider-history-timestamps.js +16 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +43 -0
- package/dist/server/server/agent/provider-launch-config.js +167 -0
- package/dist/server/server/agent/provider-notices.d.ts +4 -0
- package/dist/server/server/agent/provider-notices.js +9 -0
- package/dist/server/server/agent/provider-options.d.ts +19 -0
- package/dist/server/server/agent/provider-options.js +53 -0
- package/dist/server/server/agent/provider-refresh-deadline.d.ts +11 -0
- package/dist/server/server/agent/provider-refresh-deadline.js +65 -0
- package/dist/server/server/agent/provider-registry.d.ts +72 -0
- package/dist/server/server/agent/provider-registry.js +604 -0
- package/dist/server/server/agent/provider-runtime.d.ts +16 -0
- package/dist/server/server/agent/provider-runtime.js +35 -0
- package/dist/server/server/agent/provider-session-import.d.ts +10 -0
- package/dist/server/server/agent/provider-session-import.js +55 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +166 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +843 -0
- package/dist/server/server/agent/provider-subagents/store.d.ts +68 -0
- package/dist/server/server/agent/provider-subagents/store.js +111 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +703 -0
- package/dist/server/server/agent/providers/acp-agent.js +3412 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +74 -0
- package/dist/server/server/agent/providers/claude/agent.js +5236 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
- package/dist/server/server/agent/providers/claude/hooks.d.ts +12 -0
- package/dist/server/server/agent/providers/claude/hooks.js +20 -0
- package/dist/server/server/agent/providers/claude/model-manifest.d.ts +147 -0
- package/dist/server/server/agent/providers/claude/model-manifest.js +342 -0
- package/dist/server/server/agent/providers/claude/models.d.ts +28 -0
- package/dist/server/server/agent/providers/claude/models.js +141 -0
- package/dist/server/server/agent/providers/claude/options.d.ts +85 -0
- package/dist/server/server/agent/providers/claude/options.js +93 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
- package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
- package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
- package/dist/server/server/agent/providers/claude/query.d.ts +17 -0
- package/dist/server/server/agent/providers/claude/query.js +86 -0
- package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
- package/dist/server/server/agent/providers/claude/rewind.js +30 -0
- package/dist/server/server/agent/providers/claude/side-question.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/side-question.js +57 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +35 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +392 -0
- package/dist/server/server/agent/providers/claude/subagents/live-source.d.ts +161 -0
- package/dist/server/server/agent/providers/claude/subagents/live-source.js +415 -0
- package/dist/server/server/agent/providers/claude/subagents/observation.d.ts +53 -0
- package/dist/server/server/agent/providers/claude/subagents/observation.js +57 -0
- package/dist/server/server/agent/providers/claude/subagents/presentation.d.ts +12 -0
- package/dist/server/server/agent/providers/claude/subagents/presentation.js +41 -0
- package/dist/server/server/agent/providers/claude/subagents/replay-source.d.ts +70 -0
- package/dist/server/server/agent/providers/claude/subagents/replay-source.js +305 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-output.d.ts +4 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-output.js +86 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-replay-source.d.ts +38 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-replay-source.js +225 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +37 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +296 -0
- package/dist/server/server/agent/providers/claude/task-state.d.ts +18 -0
- package/dist/server/server/agent/providers/claude/task-state.js +185 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +101 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.js +290 -0
- package/dist/server/server/agent/providers/codex/async-questions.d.ts +18 -0
- package/dist/server/server/agent/providers/codex/async-questions.js +135 -0
- package/dist/server/server/agent/providers/codex/options.d.ts +59 -0
- package/dist/server/server/agent/providers/codex/options.js +82 -0
- package/dist/server/server/agent/providers/codex/rewind.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/rewind.js +77 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +909 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +384 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js +5877 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js +55 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js +187 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +31 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +356 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +55 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +178 -0
- package/dist/server/server/agent/providers/gjc-acp-agent.d.ts +155 -0
- package/dist/server/server/agent/providers/gjc-acp-agent.js +588 -0
- package/dist/server/server/agent/providers/jsonl-frame-decoder.d.ts +17 -0
- package/dist/server/server/agent/providers/jsonl-frame-decoder.js +149 -0
- package/dist/server/server/agent/providers/jsonl-rpc-process.d.ts +63 -0
- package/dist/server/server/agent/providers/jsonl-rpc-process.js +207 -0
- package/dist/server/server/agent/providers/kimi-acp-agent.d.ts +35 -0
- package/dist/server/server/agent/providers/kimi-acp-agent.js +65 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +111 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +1340 -0
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
- package/dist/server/server/agent/providers/mock-slow-provider.js +40 -0
- package/dist/server/server/agent/providers/omp/advisor-message.d.ts +11 -0
- package/dist/server/server/agent/providers/omp/advisor-message.js +80 -0
- package/dist/server/server/agent/providers/omp/agent.d.ts +178 -0
- package/dist/server/server/agent/providers/omp/agent.js +1952 -0
- package/dist/server/server/agent/providers/omp/cli-runtime.d.ts +22 -0
- package/dist/server/server/agent/providers/omp/cli-runtime.js +208 -0
- package/dist/server/server/agent/providers/omp/commands.d.ts +8 -0
- package/dist/server/server/agent/providers/omp/commands.js +62 -0
- package/dist/server/server/agent/providers/omp/custom-message.d.ts +7 -0
- package/dist/server/server/agent/providers/omp/custom-message.js +4 -0
- package/dist/server/server/agent/providers/omp/event-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/omp/event-mapper.js +267 -0
- package/dist/server/server/agent/providers/omp/history-hooks.d.ts +3 -0
- package/dist/server/server/agent/providers/omp/history-hooks.js +13 -0
- package/dist/server/server/agent/providers/omp/history.d.ts +19 -0
- package/dist/server/server/agent/providers/omp/history.js +306 -0
- package/dist/server/server/agent/providers/omp/host-tools.d.ts +14 -0
- package/dist/server/server/agent/providers/omp/host-tools.js +196 -0
- package/dist/server/server/agent/providers/omp/map-omp-model.d.ts +11 -0
- package/dist/server/server/agent/providers/omp/map-omp-model.js +67 -0
- package/dist/server/server/agent/providers/omp/message-history.d.ts +38 -0
- package/dist/server/server/agent/providers/omp/message-history.js +216 -0
- package/dist/server/server/agent/providers/omp/protocol-session.d.ts +13 -0
- package/dist/server/server/agent/providers/omp/protocol-session.js +39 -0
- package/dist/server/server/agent/providers/omp/provider-config.d.ts +43 -0
- package/dist/server/server/agent/providers/omp/provider-config.js +120 -0
- package/dist/server/server/agent/providers/omp/rpc-types.d.ts +2040 -0
- package/dist/server/server/agent/providers/omp/rpc-types.js +551 -0
- package/dist/server/server/agent/providers/omp/rpc-ui-permission-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/omp/rpc-ui-permission-mapper.js +156 -0
- package/dist/server/server/agent/providers/omp/runtime.d.ts +85 -0
- package/dist/server/server/agent/providers/omp/runtime.js +62 -0
- package/dist/server/server/agent/providers/omp/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/omp/session-descriptor.js +389 -0
- package/dist/server/server/agent/providers/omp/subagent-card-tracker.d.ts +33 -0
- package/dist/server/server/agent/providers/omp/subagent-card-tracker.js +264 -0
- package/dist/server/server/agent/providers/omp/subagent-index.d.ts +13 -0
- package/dist/server/server/agent/providers/omp/subagent-index.js +111 -0
- package/dist/server/server/agent/providers/omp/subagent-title.d.ts +2 -0
- package/dist/server/server/agent/providers/omp/subagent-title.js +14 -0
- package/dist/server/server/agent/providers/omp/system-notice.d.ts +8 -0
- package/dist/server/server/agent/providers/omp/system-notice.js +68 -0
- package/dist/server/server/agent/providers/omp/todo-mapper.d.ts +9 -0
- package/dist/server/server/agent/providers/omp/todo-mapper.js +52 -0
- package/dist/server/server/agent/providers/omp/tool-call-detail.d.ts +110 -0
- package/dist/server/server/agent/providers/omp/tool-call-detail.js +288 -0
- package/dist/server/server/agent/providers/omp/tool-call-id.d.ts +4 -0
- package/dist/server/server/agent/providers/omp/tool-call-id.js +34 -0
- package/dist/server/server/agent/providers/omp/tool-call-mapper.d.ts +7 -0
- package/dist/server/server/agent/providers/omp/tool-call-mapper.js +98 -0
- package/dist/server/server/agent/providers/omp/usage-poller.d.ts +32 -0
- package/dist/server/server/agent/providers/omp/usage-poller.js +141 -0
- package/dist/server/server/agent/providers/opencode/bridge-plugin.bundle.mjs +14678 -0
- package/dist/server/server/agent/providers/opencode/bridge.d.ts +39 -0
- package/dist/server/server/agent/providers/opencode/bridge.js +249 -0
- package/dist/server/server/agent/providers/opencode/event-consumer.d.ts +60 -0
- package/dist/server/server/agent/providers/opencode/event-consumer.js +221 -0
- package/dist/server/server/agent/providers/opencode/options.d.ts +156 -0
- package/dist/server/server/agent/providers/opencode/options.js +62 -0
- package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/paths.js +7 -0
- package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +101 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +513 -0
- package/dist/server/server/agent/providers/opencode/side-question.d.ts +78 -0
- package/dist/server/server/agent/providers/opencode/side-question.js +172 -0
- package/dist/server/server/agent/providers/opencode/subagent-presentation.d.ts +36 -0
- package/dist/server/server/agent/providers/opencode/subagent-presentation.js +62 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +22 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.js +43 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +393 -0
- package/dist/server/server/agent/providers/opencode-agent.js +4106 -0
- package/dist/server/server/agent/providers/pi/agent.d.ts +164 -0
- package/dist/server/server/agent/providers/pi/agent.js +2236 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.js +177 -0
- package/dist/server/server/agent/providers/pi/history-mapper.d.ts +36 -0
- package/dist/server/server/agent/providers/pi/history-mapper.js +212 -0
- package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
- package/dist/server/server/agent/providers/pi/rewind.js +8 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +252 -0
- package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
- package/dist/server/server/agent/providers/pi/runtime.d.ts +77 -0
- package/dist/server/server/agent/providers/pi/runtime.js +65 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +355 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +113 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.js +340 -0
- package/dist/server/server/agent/providers/pi/usage-poller.d.ts +32 -0
- package/dist/server/server/agent/providers/pi/usage-poller.js +141 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
- package/dist/server/server/agent/providers/provider-image-output.js +165 -0
- package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
- package/dist/server/server/agent/providers/provider-runner.js +81 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
- package/dist/server/server/agent/providers/trae-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/trae-acp-agent.js +18 -0
- package/dist/server/server/agent/recordings-debug.d.ts +3 -0
- package/dist/server/server/agent/recordings-debug.js +19 -0
- package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
- package/dist/server/server/agent/rewind/rewind.js +29 -0
- package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
- package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
- package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
- package/dist/server/server/agent/runtime-mcp-config.js +53 -0
- package/dist/server/server/agent/side-conversations/store.d.ts +33 -0
- package/dist/server/server/agent/side-conversations/store.js +62 -0
- package/dist/server/server/agent/stale-provider-session-error.d.ts +6 -0
- package/dist/server/server/agent/stale-provider-session-error.js +11 -0
- package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
- package/dist/server/server/agent/structured-generation-providers.js +193 -0
- package/dist/server/server/agent/stt-debug.d.ts +10 -0
- package/dist/server/server/agent/stt-debug.js +33 -0
- package/dist/server/server/agent/stt-manager.d.ts +38 -0
- package/dist/server/server/agent/stt-manager.js +246 -0
- package/dist/server/server/agent/system-prompt.d.ts +2 -0
- package/dist/server/server/agent/system-prompt.js +8 -0
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-page-bounds.d.ts +19 -0
- package/dist/server/server/agent/timeline-page-bounds.js +37 -0
- package/dist/server/server/agent/timeline-projection.d.ts +74 -0
- package/dist/server/server/agent/timeline-projection.js +467 -0
- package/dist/server/server/agent/timeline-prompt-index.d.ts +12 -0
- package/dist/server/server/agent/timeline-prompt-index.js +23 -0
- package/dist/server/server/agent/tools/paseo-tool-serialization.d.ts +4 -0
- package/dist/server/server/agent/tools/paseo-tool-serialization.js +58 -0
- package/dist/server/server/agent/tools/paseo-tools.d.ts +62 -0
- package/dist/server/server/agent/tools/paseo-tools.js +2445 -0
- package/dist/server/server/agent/tools/types.d.ts +39 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/agent/tts-debug.d.ts +8 -0
- package/dist/server/server/agent/tts-debug.js +24 -0
- package/dist/server/server/agent/tts-manager.d.ts +41 -0
- package/dist/server/server/agent/tts-manager.js +375 -0
- package/dist/server/server/agent-attention-policy.d.ts +29 -0
- package/dist/server/server/agent-attention-policy.js +36 -0
- package/dist/server/server/agent-history-search.d.ts +48 -0
- package/dist/server/server/agent-history-search.js +129 -0
- package/dist/server/server/atomic-file.d.ts +3 -0
- package/dist/server/server/atomic-file.js +19 -0
- package/dist/server/server/auth.d.ts +38 -0
- package/dist/server/server/auth.js +122 -0
- package/dist/server/server/authorization/index.d.ts +18 -0
- package/dist/server/server/authorization/index.js +47 -0
- package/dist/server/server/authorization/operation-permissions.d.ts +8 -0
- package/dist/server/server/authorization/operation-permissions.js +438 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +64 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts +13 -0
- package/dist/server/server/auto-archive-on-merge/index.js +77 -0
- package/dist/server/server/bootstrap.d.ts +166 -0
- package/dist/server/server/bootstrap.js +1388 -0
- package/dist/server/server/browser-tools/broker.d.ts +45 -0
- package/dist/server/server/browser-tools/broker.js +390 -0
- package/dist/server/server/browser-tools/errors.d.ts +22 -0
- package/dist/server/server/browser-tools/errors.js +27 -0
- package/dist/server/server/browser-tools/policy.d.ts +10 -0
- package/dist/server/server/browser-tools/policy.js +16 -0
- package/dist/server/server/browser-tools/tools.d.ts +16 -0
- package/dist/server/server/browser-tools/tools.js +796 -0
- package/dist/server/server/checkout/status-projection.d.ts +19 -0
- package/dist/server/server/checkout/status-projection.js +127 -0
- package/dist/server/server/checkout-diff-manager.d.ts +48 -0
- package/dist/server/server/checkout-diff-manager.js +284 -0
- package/dist/server/server/checkout-git-utils.d.ts +11 -0
- package/dist/server/server/checkout-git-utils.js +34 -0
- package/dist/server/server/client-message-id.d.ts +3 -0
- package/dist/server/server/client-message-id.js +12 -0
- package/dist/server/server/config.d.ts +26 -0
- package/dist/server/server/config.js +594 -0
- package/dist/server/server/connection-offer.d.ts +20 -0
- package/dist/server/server/connection-offer.js +59 -0
- package/dist/server/server/creation/index.d.ts +41 -0
- package/dist/server/server/creation/index.js +250 -0
- package/dist/server/server/daemon-config-store.d.ts +63 -0
- package/dist/server/server/daemon-config-store.js +524 -0
- package/dist/server/server/daemon-keypair.d.ts +8 -0
- package/dist/server/server/daemon-keypair.js +42 -0
- package/dist/server/server/daemon-version.d.ts +5 -0
- package/dist/server/server/daemon-version.js +22 -0
- package/dist/server/server/daemon-worker.d.ts +2 -0
- package/dist/server/server/daemon-worker.js +287 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +88 -0
- package/dist/server/server/dictation/dictation-stream-manager.js +578 -0
- package/dist/server/server/directory-sync/index.d.ts +45 -0
- package/dist/server/server/directory-sync/index.js +98 -0
- package/dist/server/server/directory-sync/internal/versioned-collection.d.ts +56 -0
- package/dist/server/server/directory-sync/internal/versioned-collection.js +94 -0
- package/dist/server/server/exports.d.ts +24 -0
- package/dist/server/server/exports.js +24 -0
- package/dist/server/server/file-download/token-store.d.ts +24 -0
- package/dist/server/server/file-download/token-store.js +40 -0
- package/dist/server/server/file-explorer/observer.d.ts +32 -0
- package/dist/server/server/file-explorer/observer.js +130 -0
- package/dist/server/server/file-explorer/service.d.ts +125 -0
- package/dist/server/server/file-explorer/service.js +663 -0
- package/dist/server/server/file-observer/index.d.ts +43 -0
- package/dist/server/server/file-observer/index.js +114 -0
- package/dist/server/server/file-observer/internal/contracts.d.ts +26 -0
- package/dist/server/server/file-observer/internal/contracts.js +2 -0
- package/dist/server/server/file-observer/internal/linux.d.ts +4 -0
- package/dist/server/server/file-observer/internal/linux.js +260 -0
- package/dist/server/server/file-observer/internal/native-recursive.d.ts +9 -0
- package/dist/server/server/file-observer/internal/native-recursive.js +577 -0
- package/dist/server/server/file-observer/internal/observation.d.ts +21 -0
- package/dist/server/server/file-observer/internal/observation.js +129 -0
- package/dist/server/server/file-observer/internal/paths.d.ts +10 -0
- package/dist/server/server/file-observer/internal/paths.js +43 -0
- package/dist/server/server/file-upload/index.d.ts +27 -0
- package/dist/server/server/file-upload/index.js +158 -0
- package/dist/server/server/git-metadata-event-rules.d.ts +123 -0
- package/dist/server/server/git-metadata-event-rules.js +152 -0
- package/dist/server/server/hostnames.d.ts +13 -0
- package/dist/server/server/hostnames.js +94 -0
- package/dist/server/server/hub/daemon-executions.d.ts +89 -0
- package/dist/server/server/hub/daemon-executions.js +287 -0
- package/dist/server/server/hub/execution-controller.d.ts +30 -0
- package/dist/server/server/hub/execution-controller.js +220 -0
- package/dist/server/server/hub/relationship-controller.d.ts +116 -0
- package/dist/server/server/hub/relationship-controller.js +534 -0
- package/dist/server/server/hub/relationship-remote.d.ts +68 -0
- package/dist/server/server/hub/relationship-remote.js +159 -0
- package/dist/server/server/hub/relationship-retry.d.ts +7 -0
- package/dist/server/server/hub/relationship-retry.js +15 -0
- package/dist/server/server/json-utils.d.ts +11 -0
- package/dist/server/server/json-utils.js +45 -0
- package/dist/server/server/lifecycle-reasons.d.ts +4 -0
- package/dist/server/server/lifecycle-reasons.js +6 -0
- package/dist/server/server/logger.d.ts +29 -0
- package/dist/server/server/logger.js +127 -0
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/messages.d.ts +9 -0
- package/dist/server/server/messages.js +29 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
- package/dist/server/server/orchestration-skills/index.d.ts +18 -0
- package/dist/server/server/orchestration-skills/index.js +18 -0
- package/dist/server/server/orchestration-skills/internal/controller.d.ts +39 -0
- package/dist/server/server/orchestration-skills/internal/controller.js +108 -0
- package/dist/server/server/orchestration-skills/internal/operations.d.ts +43 -0
- package/dist/server/server/orchestration-skills/internal/operations.js +178 -0
- package/dist/server/server/orchestration-skills/internal/paths.d.ts +4 -0
- package/dist/server/server/orchestration-skills/internal/paths.js +21 -0
- package/dist/server/server/orchestration-skills/internal/selection-store.d.ts +12 -0
- package/dist/server/server/orchestration-skills/internal/selection-store.js +39 -0
- package/dist/server/server/orchestration-skills/internal/sync.d.ts +21 -0
- package/dist/server/server/orchestration-skills/internal/sync.js +177 -0
- package/dist/server/server/orchestration-skills/internal/transaction.d.ts +22 -0
- package/dist/server/server/orchestration-skills/internal/transaction.js +483 -0
- package/dist/server/server/package-version.d.ts +19 -0
- package/dist/server/server/package-version.js +60 -0
- package/dist/server/server/pagination/cursor.d.ts +16 -0
- package/dist/server/server/pagination/cursor.js +62 -0
- package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
- package/dist/server/server/pagination/sortable-pager.js +68 -0
- package/dist/server/server/pairing-offer.d.ts +18 -0
- package/dist/server/server/pairing-offer.js +47 -0
- package/dist/server/server/pairing-qr.d.ts +2 -0
- package/dist/server/server/pairing-qr.js +15 -0
- package/dist/server/server/paseo-env.d.ts +13 -0
- package/dist/server/server/paseo-env.js +54 -0
- package/dist/server/server/paseo-home.d.ts +2 -0
- package/dist/server/server/paseo-home.js +19 -0
- package/dist/server/server/paseo-worktree-service.d.ts +45 -0
- package/dist/server/server/paseo-worktree-service.js +191 -0
- package/dist/server/server/path-utils.d.ts +5 -0
- package/dist/server/server/path-utils.js +34 -0
- package/dist/server/server/persisted-config.d.ts +397 -0
- package/dist/server/server/persisted-config.js +411 -0
- package/dist/server/server/persistence-hooks.d.ts +32 -0
- package/dist/server/server/persistence-hooks.js +96 -0
- package/dist/server/server/pid-lock.d.ts +44 -0
- package/dist/server/server/pid-lock.js +285 -0
- package/dist/server/server/plugins/compiler-imports.d.ts +13 -0
- package/dist/server/server/plugins/compiler-imports.js +90 -0
- package/dist/server/server/plugins/compiler.d.ts +10 -0
- package/dist/server/server/plugins/compiler.js +354 -0
- package/dist/server/server/plugins/daemon-transport.d.ts +9 -0
- package/dist/server/server/plugins/daemon-transport.js +68 -0
- package/dist/server/server/plugins/index.d.ts +102 -0
- package/dist/server/server/plugins/index.js +575 -0
- package/dist/server/server/plugins/lifecycle/index.d.ts +40 -0
- package/dist/server/server/plugins/lifecycle/index.js +200 -0
- package/dist/server/server/plugins/managed-source.d.ts +50 -0
- package/dist/server/server/plugins/managed-source.js +327 -0
- package/dist/server/server/plugins/manifest.d.ts +12 -0
- package/dist/server/server/plugins/manifest.js +26 -0
- package/dist/server/server/plugins/plugin-process-protocol.d.ts +120 -0
- package/dist/server/server/plugins/plugin-process-protocol.js +154 -0
- package/dist/server/server/plugins/plugin-process.d.ts +2 -0
- package/dist/server/server/plugins/plugin-process.js +419 -0
- package/dist/server/server/plugins/plugin-sdk-specifiers.d.ts +5 -0
- package/dist/server/server/plugins/plugin-sdk-specifiers.js +23 -0
- package/dist/server/server/plugins/plugin-session-identity.d.ts +4 -0
- package/dist/server/server/plugins/plugin-session-identity.js +11 -0
- package/dist/server/server/plugins/preparation.d.ts +4 -0
- package/dist/server/server/plugins/preparation.js +49 -0
- package/dist/server/server/plugins/provider-icon.d.ts +2 -0
- package/dist/server/server/plugins/provider-icon.js +55 -0
- package/dist/server/server/plugins/runtime.d.ts +83 -0
- package/dist/server/server/plugins/runtime.js +836 -0
- package/dist/server/server/plugins/session-socket.d.ts +23 -0
- package/dist/server/server/plugins/session-socket.js +82 -0
- package/dist/server/server/plugins/settings/index.d.ts +96 -0
- package/dist/server/server/plugins/settings/index.js +115 -0
- package/dist/server/server/private-files.d.ts +6 -0
- package/dist/server/server/private-files.js +37 -0
- package/dist/server/server/process-diagnostics.d.ts +17 -0
- package/dist/server/server/process-diagnostics.js +22 -0
- package/dist/server/server/project-directory-service.d.ts +30 -0
- package/dist/server/server/project-directory-service.js +120 -0
- package/dist/server/server/project-key.d.ts +14 -0
- package/dist/server/server/project-key.js +44 -0
- package/dist/server/server/push/index.d.ts +16 -0
- package/dist/server/server/push/index.js +26 -0
- package/dist/server/server/push/push-service.d.ts +19 -0
- package/dist/server/server/push/push-service.js +66 -0
- package/dist/server/server/push/token-store.d.ts +22 -0
- package/dist/server/server/push/token-store.js +125 -0
- package/dist/server/server/relay-runtime.d.ts +27 -0
- package/dist/server/server/relay-runtime.js +46 -0
- package/dist/server/server/relay-transport.d.ts +33 -0
- package/dist/server/server/relay-transport.js +481 -0
- package/dist/server/server/request-receipts/index.d.ts +33 -0
- package/dist/server/server/request-receipts/index.js +131 -0
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +44 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +186 -0
- package/dist/server/server/schedule/cron.d.ts +4 -0
- package/dist/server/server/schedule/cron.js +83 -0
- package/dist/server/server/schedule/service.d.ts +75 -0
- package/dist/server/server/schedule/service.js +810 -0
- package/dist/server/server/schedule/store.d.ts +27 -0
- package/dist/server/server/schedule/store.js +190 -0
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.js +159 -0
- package/dist/server/server/script-proxy.d.ts +3 -0
- package/dist/server/server/script-proxy.js +2 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.js +11 -0
- package/dist/server/server/script-status-projection.d.ts +36 -0
- package/dist/server/server/script-status-projection.js +200 -0
- package/dist/server/server/server-id.d.ts +17 -0
- package/dist/server/server/server-id.js +68 -0
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +679 -0
- package/dist/server/server/session/agent-config/agent-config-session.d.ts +75 -0
- package/dist/server/server/session/agent-config/agent-config-session.js +151 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +65 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +266 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +150 -0
- package/dist/server/server/session/checkout/checkout-session.js +1187 -0
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +34 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +90 -0
- package/dist/server/server/session/daemon/daemon-self-updater.d.ts +33 -0
- package/dist/server/server/session/daemon/daemon-self-updater.js +60 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +90 -0
- package/dist/server/server/session/daemon/daemon-session.js +224 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +431 -0
- package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
- package/dist/server/server/session/daemon/install-origin.js +64 -0
- package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
- package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
- package/dist/server/server/session/files/workspace-files-session.d.ts +55 -0
- package/dist/server/server/session/files/workspace-files-session.js +359 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +32 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +74 -0
- package/dist/server/server/session/project-config/project-config-session.d.ts +35 -0
- package/dist/server/server/session/project-config/project-config-session.js +143 -0
- package/dist/server/server/session/project-config/worktree-setup-commit-status.d.ts +6 -0
- package/dist/server/server/session/project-config/worktree-setup-commit-status.js +38 -0
- package/dist/server/server/session/provider/provider-catalog-session.d.ts +78 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +388 -0
- package/dist/server/server/session/schedule/schedule-session.d.ts +47 -0
- package/dist/server/server/session/schedule/schedule-session.js +188 -0
- package/dist/server/server/session/voice/voice-session.d.ts +166 -0
- package/dist/server/server/session/voice/voice-session.js +904 -0
- package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
- package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +42 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +170 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +59 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +326 -0
- package/dist/server/server/session/workspace-recovery/workspace-recovery-service.d.ts +30 -0
- package/dist/server/server/session/workspace-recovery/workspace-recovery-service.js +178 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +54 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +156 -0
- package/dist/server/server/session.d.ts +555 -0
- package/dist/server/server/session.js +6540 -0
- package/dist/server/server/speech/audio.d.ts +10 -0
- package/dist/server/server/speech/audio.js +101 -0
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/providers/local/config.d.ts +30 -0
- package/dist/server/server/speech/providers/local/config.js +134 -0
- package/dist/server/server/speech/providers/local/models.d.ts +12 -0
- package/dist/server/server/speech/providers/local/models.js +18 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
- package/dist/server/server/speech/providers/local/runtime.js +135 -0
- package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +56 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +74 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +17 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +145 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +101 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
- package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
- package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
- package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
- package/dist/server/server/speech/providers/local/worker-client.js +590 -0
- package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
- package/dist/server/server/speech/providers/local/worker-process.js +270 -0
- package/dist/server/server/speech/providers/local/worker-protocol.d.ts +95 -0
- package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
- package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/config.js +148 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
- package/dist/server/server/speech/providers/openai/runtime.js +102 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
- package/dist/server/server/speech/providers/openai/stt.js +209 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/tts.js +48 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
- package/dist/server/server/speech/speech-config-resolver.js +132 -0
- package/dist/server/server/speech/speech-provider.d.ts +59 -0
- package/dist/server/server/speech/speech-provider.js +2 -0
- package/dist/server/server/speech/speech-runtime.d.ts +45 -0
- package/dist/server/server/speech/speech-runtime.js +571 -0
- package/dist/server/server/speech/speech-types.d.ts +22 -0
- package/dist/server/server/speech/speech-types.js +8 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
- package/dist/server/server/speech/turn-detection-provider.js +2 -0
- package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
- package/dist/server/server/utils/diff-highlighter.js +304 -0
- package/dist/server/server/voice-config.d.ts +15 -0
- package/dist/server/server/voice-config.js +54 -0
- package/dist/server/server/voice-permission-policy.d.ts +4 -0
- package/dist/server/server/voice-permission-policy.js +13 -0
- package/dist/server/server/voice-types.d.ts +12 -0
- package/dist/server/server/voice-types.js +2 -0
- package/dist/server/server/watcher-liveness-canary.d.ts +11 -0
- package/dist/server/server/watcher-liveness-canary.js +53 -0
- package/dist/server/server/web-ui.d.ts +10 -0
- package/dist/server/server/web-ui.js +205 -0
- package/dist/server/server/websocket/encrypted-relay-socket.d.ts +23 -0
- package/dist/server/server/websocket/encrypted-relay-socket.js +64 -0
- package/dist/server/server/websocket/physical-socket.d.ts +36 -0
- package/dist/server/server/websocket/physical-socket.js +95 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +95 -0
- package/dist/server/server/websocket/runtime-metrics.js +148 -0
- package/dist/server/server/websocket-server.d.ts +214 -0
- package/dist/server/server/websocket-server.js +2258 -0
- package/dist/server/server/workspace-archive-service.d.ts +63 -0
- package/dist/server/server/workspace-archive-service.js +355 -0
- package/dist/server/server/workspace-auto-name.d.ts +55 -0
- package/dist/server/server/workspace-auto-name.js +127 -0
- package/dist/server/server/workspace-automation-gate.d.ts +16 -0
- package/dist/server/server/workspace-automation-gate.js +35 -0
- package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
- package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
- package/dist/server/server/workspace-directory.d.ts +99 -0
- package/dist/server/server/workspace-directory.js +467 -0
- package/dist/server/server/workspace-git-fetch.d.ts +33 -0
- package/dist/server/server/workspace-git-fetch.js +108 -0
- package/dist/server/server/workspace-git-metadata.d.ts +8 -0
- package/dist/server/server/workspace-git-metadata.js +24 -0
- package/dist/server/server/workspace-git-service.d.ts +348 -0
- package/dist/server/server/workspace-git-service.js +2534 -0
- package/dist/server/server/workspace-labels/index.d.ts +14 -0
- package/dist/server/server/workspace-labels/index.js +10 -0
- package/dist/server/server/workspace-labels/internal/catalog-store.d.ts +50 -0
- package/dist/server/server/workspace-labels/internal/catalog-store.js +203 -0
- package/dist/server/server/workspace-labels/internal/sequence.d.ts +43 -0
- package/dist/server/server/workspace-labels/internal/sequence.js +80 -0
- package/dist/server/server/workspace-labels/internal/service.d.ts +57 -0
- package/dist/server/server/workspace-labels/internal/service.js +224 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts +103 -0
- package/dist/server/server/workspace-reconciliation-service.js +379 -0
- package/dist/server/server/workspace-registry-bootstrap-legacy.d.ts +21 -0
- package/dist/server/server/workspace-registry-bootstrap-legacy.js +57 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts +14 -0
- package/dist/server/server/workspace-registry-bootstrap.js +155 -0
- package/dist/server/server/workspace-registry-model.d.ts +56 -0
- package/dist/server/server/workspace-registry-model.js +150 -0
- package/dist/server/server/workspace-registry.d.ts +228 -0
- package/dist/server/server/workspace-registry.js +468 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-service-env.d.ts +18 -0
- package/dist/server/server/workspace-service-env.js +85 -0
- package/dist/server/server/workspace-service-port-allocator.d.ts +11 -0
- package/dist/server/server/workspace-service-port-allocator.js +89 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +24 -0
- package/dist/server/server/workspace-service-port-registry.js +172 -0
- package/dist/server/server/workspace-setup-runtime.d.ts +7 -0
- package/dist/server/server/workspace-setup-runtime.js +30 -0
- package/dist/server/server/workspace-source.d.ts +7 -0
- package/dist/server/server/workspace-source.js +15 -0
- package/dist/server/server/worktree/commands.d.ts +55 -0
- package/dist/server/server/worktree/commands.js +99 -0
- package/dist/server/server/worktree-bootstrap.d.ts +86 -0
- package/dist/server/server/worktree-bootstrap.js +753 -0
- package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
- package/dist/server/server/worktree-branch-name-generator.js +89 -0
- package/dist/server/server/worktree-core.d.ts +32 -0
- package/dist/server/server/worktree-core.js +114 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.js +28 -0
- package/dist/server/server/worktree-session.d.ts +165 -0
- package/dist/server/server/worktree-session.js +594 -0
- package/dist/server/services/forge-cli-command.d.ts +128 -0
- package/dist/server/services/forge-cli-command.js +212 -0
- package/dist/server/services/forge-registry.d.ts +25 -0
- package/dist/server/services/forge-registry.js +155 -0
- package/dist/server/services/forge-resolver.d.ts +38 -0
- package/dist/server/services/forge-resolver.js +191 -0
- package/dist/server/services/forge-service.d.ts +430 -0
- package/dist/server/services/forge-service.js +50 -0
- package/dist/server/services/gitea-facts.d.ts +11 -0
- package/dist/server/services/gitea-facts.js +4 -0
- package/dist/server/services/gitea-service.d.ts +62 -0
- package/dist/server/services/gitea-service.js +1844 -0
- package/dist/server/services/github-facts.d.ts +27 -0
- package/dist/server/services/github-facts.js +4 -0
- package/dist/server/services/github-service.d.ts +76 -0
- package/dist/server/services/github-service.js +3297 -0
- package/dist/server/services/gitlab-facts.d.ts +26 -0
- package/dist/server/services/gitlab-facts.js +4 -0
- package/dist/server/services/gitlab-service.d.ts +43 -0
- package/dist/server/services/gitlab-service.js +1092 -0
- package/dist/server/services/quota-fetcher/manifest.d.ts +5 -0
- package/dist/server/services/quota-fetcher/manifest.js +118 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +18 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +40 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +389 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +167 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +21 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +77 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +19 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +185 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +137 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +260 -0
- package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
- package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +34 -0
- package/dist/server/services/quota-fetcher/service.js +74 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +39 -0
- package/dist/server/services/quota-fetcher/usage.js +78 -0
- package/dist/server/skills/paseo/SKILL.md +129 -0
- package/dist/server/skills/paseo-advisor/SKILL.md +63 -0
- package/dist/server/skills/paseo-committee/SKILL.md +46 -0
- package/dist/server/skills/paseo-handoff/SKILL.md +65 -0
- package/dist/server/skills/paseo-help/SKILL.md +92 -0
- package/dist/server/skills/paseo-plugin/SKILL.md +639 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +21 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +65 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +81 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/server/terminal/terminal-capture.d.ts +12 -0
- package/dist/server/terminal/terminal-capture.js +43 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
- package/dist/server/terminal/terminal-manager-factory.js +5 -0
- package/dist/server/terminal/terminal-manager.d.ts +80 -0
- package/dist/server/terminal/terminal-manager.js +321 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
- package/dist/server/terminal/terminal-output-coalescer.js +73 -0
- package/dist/server/terminal/terminal-restore.d.ts +18 -0
- package/dist/server/terminal/terminal-restore.js +52 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +90 -0
- package/dist/server/terminal/terminal-session-controller.js +849 -0
- package/dist/server/terminal/terminal-size-ownership.d.ts +9 -0
- package/dist/server/terminal/terminal-size-ownership.js +23 -0
- package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
- package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
- package/dist/server/terminal/terminal-worker-process.js +308 -0
- package/dist/server/terminal/terminal-worker-protocol.d.ts +121 -0
- package/dist/server/terminal/terminal-worker-protocol.js +2 -0
- package/dist/server/terminal/terminal.d.ts +151 -0
- package/dist/server/terminal/terminal.js +1234 -0
- package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
- package/dist/server/terminal/worker-terminal-manager.js +688 -0
- package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
- package/dist/server/utils/build-metadata-prompt.js +29 -0
- package/dist/server/utils/checkout-git.d.ts +317 -0
- package/dist/server/utils/checkout-git.js +2886 -0
- package/dist/server/utils/directory-suggestions.d.ts +29 -0
- package/dist/server/utils/directory-suggestions.js +596 -0
- package/dist/server/utils/git-command-runtime-metrics.d.ts +67 -0
- package/dist/server/utils/git-command-runtime-metrics.js +126 -0
- package/dist/server/utils/git-command-trace.d.ts +29 -0
- package/dist/server/utils/git-command-trace.js +74 -0
- package/dist/server/utils/git-process-scheduler.d.ts +34 -0
- package/dist/server/utils/git-process-scheduler.js +112 -0
- package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
- package/dist/server/utils/git-rev-parse-path.js +21 -0
- package/dist/server/utils/paseo-config-file.d.ts +30 -0
- package/dist/server/utils/paseo-config-file.js +90 -0
- package/dist/server/utils/path.d.ts +27 -0
- package/dist/server/utils/path.js +156 -0
- package/dist/server/utils/project-custom-icon.d.ts +40 -0
- package/dist/server/utils/project-custom-icon.js +144 -0
- package/dist/server/utils/project-icon.d.ts +54 -0
- package/dist/server/utils/project-icon.js +445 -0
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/run-git-command.d.ts +59 -0
- package/dist/server/utils/run-git-command.js +393 -0
- package/dist/server/utils/spawn.d.ts +26 -0
- package/dist/server/utils/spawn.js +60 -0
- package/dist/server/utils/ssh-hostname.d.ts +8 -0
- package/dist/server/utils/ssh-hostname.js +60 -0
- package/dist/server/utils/string-command-shell.d.ts +13 -0
- package/dist/server/utils/string-command-shell.js +37 -0
- package/dist/server/utils/tree-kill.d.ts +19 -0
- package/dist/server/utils/tree-kill.js +77 -0
- package/dist/server/utils/windows-command.d.ts +15 -0
- package/dist/server/utils/windows-command.js +41 -0
- package/dist/server/utils/worktree-metadata.d.ts +77 -0
- package/dist/server/utils/worktree-metadata.js +280 -0
- package/dist/server/utils/worktree.d.ts +232 -0
- package/dist/server/utils/worktree.js +1193 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-29f33408b1a3c5b435c5202d62a36719.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-29f33408b1a3c5b435c5202d62a36719.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-29f33408b1a3c5b435c5202d62a36719.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-41547ee520dfff946caa727824742a5c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-41547ee520dfff946caa727824742a5c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-41547ee520dfff946caa727824742a5c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-03b99be4265ac4a2bc5a6d602fd59cc4.js +16819 -0
- package/dist/server/web-ui/_expo/static/js/web/index-03b99be4265ac4a2bc5a6d602fd59cc4.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-03b99be4265ac4a2bc5a6d602fd59cc4.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-5444610146b0794f1e7d9edad4522d41.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-5444610146b0794f1e7d9edad4522d41.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-5444610146b0794f1e7d9edad4522d41.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-88b083190031a44788be59103814f511.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-88b083190031a44788be59103814f511.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-88b083190031a44788be59103814f511.js.gz +0 -0
- package/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
- package/dist/server/web-ui/favicon.ico +0 -0
- package/dist/server/web-ui/index.html +90 -0
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/server/web-ui/manifest.json +27 -0
- package/dist/server/web-ui/manifest.json.br +0 -0
- package/dist/server/web-ui/manifest.json.gz +0 -0
- package/dist/server/web-ui/metadata.json +1 -0
- package/dist/server/web-ui/metadata.json.br +1 -0
- package/dist/server/web-ui/metadata.json.gz +0 -0
- package/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/dist/server/web-ui/robots.txt +2 -0
- package/dist/src/executable-resolution/executable-resolution.js +121 -0
- package/dist/src/executable-resolution/windows.js +65 -0
- package/dist/src/server/agent/provider-launch-config.js +167 -0
- package/dist/src/server/paseo-env.js +54 -0
- package/dist/src/server/paseo-home.js +19 -0
- package/dist/src/server/persisted-config.js +411 -0
- package/dist/src/server/pid-lock.js +285 -0
- package/dist/src/server/private-files.js +37 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/src/utils/spawn.js +60 -0
- package/dist/src/utils/tree-kill.js +77 -0
- package/dist/src/utils/windows-command.js +41 -0
- package/package.json +125 -0
|
@@ -0,0 +1,3297 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isGitHubHost, parseGitHubRemoteUrl, parseGitRemoteLocation, } from "@camerontaylor/paseo-protocol/git-remote";
|
|
3
|
+
import { findExecutable } from "../executable-resolution/executable-resolution.js";
|
|
4
|
+
import { runGitCommand } from "../utils/run-git-command.js";
|
|
5
|
+
import { execCommand } from "../utils/spawn.js";
|
|
6
|
+
import { resolveSshHostname } from "../utils/ssh-hostname.js";
|
|
7
|
+
import { CLI_AUTH_PROBE_TIMEOUT_MS, createForgeCliRunner, ForgeAuthenticationError, ForgeCliMissingError, parseCliJsonOutput, probeHostViaCliAuthStatus, ForgeCommandError, } from "./forge-cli-command.js";
|
|
8
|
+
import { computeChecksStatus, compareTimelineItems, createUnavailableSearchResult, normalizeForgeSearchKinds, parseOptionalTime, } from "./forge-service.js";
|
|
9
|
+
import { isGitHubPullRequestStatusFacts, } from "./github-facts.js";
|
|
10
|
+
const DEFAULT_GITHUB_CACHE_TTL_MS = 30000;
|
|
11
|
+
const CHECK_ANNOTATION_PAGE_MAX = 20;
|
|
12
|
+
const CHECK_LOG_TAIL_MAX_LINES = 200;
|
|
13
|
+
const CHECK_LOG_TAIL_MAX_BYTES = 16 * 1024;
|
|
14
|
+
const CHECK_LOG_TAIL_CACHE_MAX_ENTRIES = 128;
|
|
15
|
+
const ACTIONS_JOB_PAGE_MAX = 100;
|
|
16
|
+
const FAILED_CHECK_JOB_LIMIT = 5;
|
|
17
|
+
export const GITHUB_POLL_FAST_INTERVAL_MS = 20000;
|
|
18
|
+
export const GITHUB_POLL_SLOW_INTERVAL_MS = 120000;
|
|
19
|
+
export const GITHUB_POLL_ERROR_BACKOFF_CAP_MS = 300000;
|
|
20
|
+
// The PR status poller batches every due target into aliased GraphQL queries
|
|
21
|
+
// instead of one `gh pr view` + facts query per workspace. Cost scales with the
|
|
22
|
+
// GraphQL point budget (5,000/hour), so the batch size and the shared poll time
|
|
23
|
+
// grid below exist to keep a many-workspace daemon from exhausting the quota
|
|
24
|
+
// machine-wide (issues #3587 / #2470).
|
|
25
|
+
export const GITHUB_POLL_BATCH_MAX = 25;
|
|
26
|
+
export const GITHUB_POLL_ALIGNMENT_MS = 5000;
|
|
27
|
+
const GITHUB_GRAPHQL_RESERVE_RATIO = 0.4;
|
|
28
|
+
const GITHUB_GRAPHQL_RESET_GRACE_MS = 1000;
|
|
29
|
+
const BATCH_PR_CANDIDATE_LIMIT = 10;
|
|
30
|
+
const FROZEN_PR_CHECKS_CACHE_MAX_ENTRIES = 512;
|
|
31
|
+
const GITHUB_ENV = {
|
|
32
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
33
|
+
};
|
|
34
|
+
// Matches the glab/tea adapters' command timeout so a hung `gh` invocation
|
|
35
|
+
// (e.g. a stalled network call) fails the same way across every forge.
|
|
36
|
+
const GITHUB_COMMAND_TIMEOUT_MS = 30000;
|
|
37
|
+
const REPO_HOST_NULL_TTL_MS = 60000;
|
|
38
|
+
const GIT_ORIGIN_URL_READ_TIMEOUT_MS = 5000;
|
|
39
|
+
const LabelSchema = z.object({
|
|
40
|
+
name: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
const GitHubIssueSummarySchema = z.object({
|
|
43
|
+
number: z.number(),
|
|
44
|
+
title: z.string().catch(""),
|
|
45
|
+
url: z.string().catch(""),
|
|
46
|
+
state: z.string().catch(""),
|
|
47
|
+
body: z.string().nullable().catch(null),
|
|
48
|
+
labels: z.array(LabelSchema).catch([]),
|
|
49
|
+
updatedAt: z.string().catch(""),
|
|
50
|
+
});
|
|
51
|
+
const GitHubPullRequestSummarySchema = z.object({
|
|
52
|
+
number: z.number(),
|
|
53
|
+
title: z.string().catch(""),
|
|
54
|
+
url: z.string().catch(""),
|
|
55
|
+
state: z.string().catch(""),
|
|
56
|
+
body: z.string().nullable().catch(null),
|
|
57
|
+
baseRefName: z.string().catch(""),
|
|
58
|
+
headRefName: z.string().catch(""),
|
|
59
|
+
labels: z.array(LabelSchema).catch([]),
|
|
60
|
+
updatedAt: z.string().catch(""),
|
|
61
|
+
});
|
|
62
|
+
const GitHubRepositoryListItemSchema = z.object({
|
|
63
|
+
id: z.union([z.string(), z.number()]),
|
|
64
|
+
name: z.string(),
|
|
65
|
+
nameWithOwner: z.string(),
|
|
66
|
+
description: z.string().nullable().optional(),
|
|
67
|
+
isPrivate: z.boolean(),
|
|
68
|
+
updatedAt: z.string(),
|
|
69
|
+
sshUrl: z.string(),
|
|
70
|
+
url: z.string(),
|
|
71
|
+
});
|
|
72
|
+
const GitHubRepositorySearchItemSchema = z.object({
|
|
73
|
+
id: z.union([z.string(), z.number()]),
|
|
74
|
+
name: z.string(),
|
|
75
|
+
fullName: z.string(),
|
|
76
|
+
description: z.string().nullable().optional(),
|
|
77
|
+
isPrivate: z.boolean(),
|
|
78
|
+
updatedAt: z.string(),
|
|
79
|
+
url: z.string(),
|
|
80
|
+
});
|
|
81
|
+
const PullRequestCheckRunNodeSchema = z.object({
|
|
82
|
+
__typename: z.literal("CheckRun"),
|
|
83
|
+
databaseId: z.number().nullable().optional(),
|
|
84
|
+
name: z.string(),
|
|
85
|
+
workflowName: z.string().nullable().optional(),
|
|
86
|
+
conclusion: z.string().nullable().optional(),
|
|
87
|
+
status: z.string().nullable().optional(),
|
|
88
|
+
detailsUrl: z.string().nullable().optional(),
|
|
89
|
+
startedAt: z.string().nullable().optional(),
|
|
90
|
+
completedAt: z.string().nullable().optional(),
|
|
91
|
+
checkSuite: z
|
|
92
|
+
.object({
|
|
93
|
+
workflowRun: z
|
|
94
|
+
.object({
|
|
95
|
+
databaseId: z.number().nullable().optional(),
|
|
96
|
+
})
|
|
97
|
+
.nullable()
|
|
98
|
+
.optional(),
|
|
99
|
+
})
|
|
100
|
+
.nullable()
|
|
101
|
+
.optional(),
|
|
102
|
+
});
|
|
103
|
+
const PullRequestStatusContextNodeSchema = z.object({
|
|
104
|
+
__typename: z.literal("StatusContext"),
|
|
105
|
+
context: z.string(),
|
|
106
|
+
state: z.string().nullable().optional(),
|
|
107
|
+
targetUrl: z.string().nullable().optional(),
|
|
108
|
+
createdAt: z.string().nullable().optional(),
|
|
109
|
+
});
|
|
110
|
+
const PullRequestStatusCheckRollupNodeSchema = z.discriminatedUnion("__typename", [
|
|
111
|
+
PullRequestCheckRunNodeSchema,
|
|
112
|
+
PullRequestStatusContextNodeSchema,
|
|
113
|
+
]);
|
|
114
|
+
const PullRequestStatusCheckRollupArraySchema = z.array(z.unknown());
|
|
115
|
+
const LegacyPullRequestStatusCheckRollupSchema = z.object({
|
|
116
|
+
contexts: z.array(z.unknown()),
|
|
117
|
+
});
|
|
118
|
+
const GitHubCheckRunDetailsSchema = z.object({
|
|
119
|
+
id: z.number(),
|
|
120
|
+
name: z.string().catch(""),
|
|
121
|
+
status: z.string().nullable().optional(),
|
|
122
|
+
conclusion: z.string().nullable().optional(),
|
|
123
|
+
html_url: z.string().nullable().optional(),
|
|
124
|
+
details_url: z.string().nullable().optional(),
|
|
125
|
+
output: z
|
|
126
|
+
.object({
|
|
127
|
+
title: z.string().nullable().optional(),
|
|
128
|
+
summary: z.string().nullable().optional(),
|
|
129
|
+
text: z.string().nullable().optional(),
|
|
130
|
+
})
|
|
131
|
+
.nullable()
|
|
132
|
+
.optional(),
|
|
133
|
+
check_suite: z
|
|
134
|
+
.object({
|
|
135
|
+
workflow_run: z
|
|
136
|
+
.object({
|
|
137
|
+
id: z.number().nullable().optional(),
|
|
138
|
+
})
|
|
139
|
+
.nullable()
|
|
140
|
+
.optional(),
|
|
141
|
+
})
|
|
142
|
+
.nullable()
|
|
143
|
+
.optional(),
|
|
144
|
+
});
|
|
145
|
+
const GitHubCheckAnnotationSchema = z.object({
|
|
146
|
+
path: z.string().optional(),
|
|
147
|
+
start_line: z.number().optional(),
|
|
148
|
+
end_line: z.number().optional(),
|
|
149
|
+
annotation_level: z.string().optional(),
|
|
150
|
+
message: z.string().optional(),
|
|
151
|
+
title: z.string().optional(),
|
|
152
|
+
raw_details: z.string().optional(),
|
|
153
|
+
});
|
|
154
|
+
const GitHubCheckAnnotationsSchema = z.array(GitHubCheckAnnotationSchema).catch([]);
|
|
155
|
+
const GitHubActionsJobSchema = z.object({
|
|
156
|
+
id: z.number(),
|
|
157
|
+
name: z.string().catch(""),
|
|
158
|
+
status: z.string().nullable().optional(),
|
|
159
|
+
conclusion: z.string().nullable().optional(),
|
|
160
|
+
html_url: z.string().nullable().optional(),
|
|
161
|
+
completed_at: z.string().nullable().optional(),
|
|
162
|
+
});
|
|
163
|
+
const GitHubActionsJobsSchema = z.object({
|
|
164
|
+
jobs: z.array(GitHubActionsJobSchema).catch([]),
|
|
165
|
+
});
|
|
166
|
+
const PullRequestReviewDecisionSchema = z
|
|
167
|
+
.enum(["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"])
|
|
168
|
+
.nullable()
|
|
169
|
+
.catch(null);
|
|
170
|
+
const HeadRepositoryOwnerSchema = z
|
|
171
|
+
.object({
|
|
172
|
+
login: z.string().optional(),
|
|
173
|
+
})
|
|
174
|
+
.nullable()
|
|
175
|
+
.optional();
|
|
176
|
+
const PullRequestMergeableSchema = z.enum(["MERGEABLE", "CONFLICTING", "UNKNOWN"]).catch("UNKNOWN");
|
|
177
|
+
const GitHubAutoMergeRequestSchema = z
|
|
178
|
+
.object({
|
|
179
|
+
enabledAt: z.string().nullable().optional().catch(null),
|
|
180
|
+
mergeMethod: z.string().nullable().optional().catch(null),
|
|
181
|
+
enabledBy: z
|
|
182
|
+
.object({
|
|
183
|
+
login: z.string().nullable().optional().catch(null),
|
|
184
|
+
})
|
|
185
|
+
.nullable()
|
|
186
|
+
.optional()
|
|
187
|
+
.catch(null),
|
|
188
|
+
})
|
|
189
|
+
.nullable()
|
|
190
|
+
.optional()
|
|
191
|
+
.catch(null);
|
|
192
|
+
const GitHubPullRequestFactsGraphqlSchema = z.object({
|
|
193
|
+
data: z.object({
|
|
194
|
+
repository: z
|
|
195
|
+
.object({
|
|
196
|
+
autoMergeAllowed: z.boolean().optional().catch(false),
|
|
197
|
+
mergeCommitAllowed: z.boolean().optional().catch(false),
|
|
198
|
+
squashMergeAllowed: z.boolean().optional().catch(false),
|
|
199
|
+
rebaseMergeAllowed: z.boolean().optional().catch(false),
|
|
200
|
+
viewerDefaultMergeMethod: z.string().nullable().optional().catch(null),
|
|
201
|
+
pullRequest: z
|
|
202
|
+
.object({
|
|
203
|
+
mergeStateStatus: z.string().nullable().optional().catch(null),
|
|
204
|
+
autoMergeRequest: GitHubAutoMergeRequestSchema,
|
|
205
|
+
viewerCanEnableAutoMerge: z.boolean().optional().catch(false),
|
|
206
|
+
viewerCanDisableAutoMerge: z.boolean().optional().catch(false),
|
|
207
|
+
viewerCanMergeAsAdmin: z.boolean().optional().catch(false),
|
|
208
|
+
viewerCanUpdateBranch: z.boolean().optional().catch(false),
|
|
209
|
+
isMergeQueueEnabled: z.boolean().optional().catch(false),
|
|
210
|
+
isInMergeQueue: z.boolean().optional().catch(false),
|
|
211
|
+
})
|
|
212
|
+
.nullable()
|
|
213
|
+
.optional()
|
|
214
|
+
.catch(null),
|
|
215
|
+
})
|
|
216
|
+
.nullable()
|
|
217
|
+
.optional()
|
|
218
|
+
.catch(null),
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
const CurrentPullRequestStatusSchema = z.object({
|
|
222
|
+
number: z.number().optional(),
|
|
223
|
+
url: z.string().catch(""),
|
|
224
|
+
title: z.string().catch(""),
|
|
225
|
+
state: z.string().catch(""),
|
|
226
|
+
isDraft: z.boolean().optional().catch(false),
|
|
227
|
+
baseRefName: z.string().catch(""),
|
|
228
|
+
headRefName: z.string().catch(""),
|
|
229
|
+
headRefOid: z.string().optional(),
|
|
230
|
+
mergedAt: z.string().nullable().optional(),
|
|
231
|
+
statusCheckRollup: z.unknown().optional(),
|
|
232
|
+
reviewDecision: z.unknown().optional(),
|
|
233
|
+
mergeable: PullRequestMergeableSchema.optional().default("UNKNOWN"),
|
|
234
|
+
headRepositoryOwner: HeadRepositoryOwnerSchema,
|
|
235
|
+
});
|
|
236
|
+
const TimelineAuthorSchema = z
|
|
237
|
+
.object({
|
|
238
|
+
login: z.string().optional(),
|
|
239
|
+
url: z.string().nullable().optional(),
|
|
240
|
+
avatarUrl: z.string().nullable().optional(),
|
|
241
|
+
})
|
|
242
|
+
.nullable()
|
|
243
|
+
.optional();
|
|
244
|
+
const PullRequestTimelineReviewNodeSchema = z.object({
|
|
245
|
+
id: z.string().catch(""),
|
|
246
|
+
state: z.string().catch(""),
|
|
247
|
+
body: z.string().nullable().catch(null),
|
|
248
|
+
bodyHTML: z.string().nullable().catch(null),
|
|
249
|
+
url: z.string().catch(""),
|
|
250
|
+
submittedAt: z.string().nullable().catch(null),
|
|
251
|
+
author: TimelineAuthorSchema,
|
|
252
|
+
});
|
|
253
|
+
const PullRequestTimelineCommentNodeSchema = z.object({
|
|
254
|
+
id: z.string().catch(""),
|
|
255
|
+
body: z.string().nullable().catch(null),
|
|
256
|
+
bodyHTML: z.string().nullable().catch(null),
|
|
257
|
+
url: z.string().catch(""),
|
|
258
|
+
createdAt: z.string().nullable().catch(null),
|
|
259
|
+
author: TimelineAuthorSchema,
|
|
260
|
+
});
|
|
261
|
+
const PullRequestReviewThreadCommentNodeSchema = PullRequestTimelineCommentNodeSchema.extend({
|
|
262
|
+
pullRequestReview: z
|
|
263
|
+
.object({ id: z.string().catch("") })
|
|
264
|
+
.nullable()
|
|
265
|
+
.optional()
|
|
266
|
+
.catch(null),
|
|
267
|
+
});
|
|
268
|
+
const PullRequestReviewThreadNodeSchema = z.object({
|
|
269
|
+
id: z.string().catch(""),
|
|
270
|
+
path: z.string().catch(""),
|
|
271
|
+
line: z.number().nullable().optional().catch(null),
|
|
272
|
+
startLine: z.number().nullable().optional().catch(null),
|
|
273
|
+
isResolved: z.boolean().catch(false),
|
|
274
|
+
isOutdated: z.boolean().catch(false),
|
|
275
|
+
comments: z
|
|
276
|
+
.object({
|
|
277
|
+
nodes: z.array(PullRequestReviewThreadCommentNodeSchema).catch([]),
|
|
278
|
+
pageInfo: z.object({ hasNextPage: z.boolean().catch(false) }).catch({ hasNextPage: false }),
|
|
279
|
+
})
|
|
280
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
281
|
+
});
|
|
282
|
+
const PullRequestTimelinePageInfoSchema = z.object({
|
|
283
|
+
hasNextPage: z.boolean().catch(false),
|
|
284
|
+
});
|
|
285
|
+
const PullRequestTimelineGraphqlSchema = z.object({
|
|
286
|
+
data: z
|
|
287
|
+
.object({
|
|
288
|
+
repository: z
|
|
289
|
+
.object({
|
|
290
|
+
pullRequest: z
|
|
291
|
+
.object({
|
|
292
|
+
number: z.number().optional(),
|
|
293
|
+
reviews: z
|
|
294
|
+
.object({
|
|
295
|
+
nodes: z.array(PullRequestTimelineReviewNodeSchema).catch([]),
|
|
296
|
+
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
297
|
+
})
|
|
298
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
299
|
+
comments: z
|
|
300
|
+
.object({
|
|
301
|
+
nodes: z.array(PullRequestTimelineCommentNodeSchema).catch([]),
|
|
302
|
+
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
303
|
+
})
|
|
304
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
305
|
+
reviewThreads: z
|
|
306
|
+
.object({
|
|
307
|
+
nodes: z.array(PullRequestReviewThreadNodeSchema).catch([]),
|
|
308
|
+
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
309
|
+
})
|
|
310
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
311
|
+
})
|
|
312
|
+
.nullable()
|
|
313
|
+
.optional(),
|
|
314
|
+
})
|
|
315
|
+
.nullable()
|
|
316
|
+
.optional(),
|
|
317
|
+
})
|
|
318
|
+
.optional(),
|
|
319
|
+
});
|
|
320
|
+
const GitHubRepoViewSchema = z.object({
|
|
321
|
+
owner: z
|
|
322
|
+
.object({
|
|
323
|
+
login: z.string().optional(),
|
|
324
|
+
})
|
|
325
|
+
.nullable()
|
|
326
|
+
.optional(),
|
|
327
|
+
name: z.string().optional(),
|
|
328
|
+
parent: z
|
|
329
|
+
.object({
|
|
330
|
+
owner: z
|
|
331
|
+
.object({
|
|
332
|
+
login: z.string().optional(),
|
|
333
|
+
})
|
|
334
|
+
.nullable()
|
|
335
|
+
.optional(),
|
|
336
|
+
name: z.string().optional(),
|
|
337
|
+
})
|
|
338
|
+
.nullable()
|
|
339
|
+
.optional(),
|
|
340
|
+
});
|
|
341
|
+
const PullRequestCheckoutTargetSchema = z.object({
|
|
342
|
+
data: z.object({
|
|
343
|
+
repository: z.object({
|
|
344
|
+
pullRequest: z
|
|
345
|
+
.object({
|
|
346
|
+
number: z.number(),
|
|
347
|
+
baseRefName: z.string().catch(""),
|
|
348
|
+
headRefName: z.string().catch(""),
|
|
349
|
+
isCrossRepository: z.boolean().catch(false),
|
|
350
|
+
headRepositoryOwner: z
|
|
351
|
+
.object({
|
|
352
|
+
login: z.string().catch(""),
|
|
353
|
+
})
|
|
354
|
+
.nullable()
|
|
355
|
+
.optional(),
|
|
356
|
+
headRepository: z
|
|
357
|
+
.object({
|
|
358
|
+
sshUrl: z.string().nullable().optional(),
|
|
359
|
+
url: z.string().nullable().optional(),
|
|
360
|
+
})
|
|
361
|
+
.nullable()
|
|
362
|
+
.optional(),
|
|
363
|
+
})
|
|
364
|
+
.nullable(),
|
|
365
|
+
}),
|
|
366
|
+
}),
|
|
367
|
+
});
|
|
368
|
+
const PULL_REQUEST_CHECKOUT_TARGET_QUERY = `
|
|
369
|
+
query PullRequestCheckoutTarget($owner: String!, $name: String!, $number: Int!) {
|
|
370
|
+
repository(owner: $owner, name: $name) {
|
|
371
|
+
pullRequest(number: $number) {
|
|
372
|
+
number
|
|
373
|
+
baseRefName
|
|
374
|
+
headRefName
|
|
375
|
+
isCrossRepository
|
|
376
|
+
headRepositoryOwner {
|
|
377
|
+
login
|
|
378
|
+
}
|
|
379
|
+
headRepository {
|
|
380
|
+
sshUrl
|
|
381
|
+
url
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}`;
|
|
386
|
+
const CURRENT_PR_STATUS_BASE_FIELDS = "number,url,title,state,isDraft,baseRefName,headRefName,headRefOid,mergedAt,reviewDecision,mergeable,headRepositoryOwner";
|
|
387
|
+
const CURRENT_PR_STATUS_FIELDS = `${CURRENT_PR_STATUS_BASE_FIELDS},statusCheckRollup`;
|
|
388
|
+
const PULL_REQUEST_STATUS_FACTS_QUERY = `
|
|
389
|
+
query PullRequestStatusFacts($owner: String!, $name: String!, $number: Int!) {
|
|
390
|
+
repository(owner: $owner, name: $name) {
|
|
391
|
+
autoMergeAllowed
|
|
392
|
+
mergeCommitAllowed
|
|
393
|
+
squashMergeAllowed
|
|
394
|
+
rebaseMergeAllowed
|
|
395
|
+
viewerDefaultMergeMethod
|
|
396
|
+
pullRequest(number: $number) {
|
|
397
|
+
mergeStateStatus
|
|
398
|
+
autoMergeRequest {
|
|
399
|
+
enabledAt
|
|
400
|
+
mergeMethod
|
|
401
|
+
enabledBy {
|
|
402
|
+
login
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
viewerCanEnableAutoMerge
|
|
406
|
+
viewerCanDisableAutoMerge
|
|
407
|
+
viewerCanMergeAsAdmin
|
|
408
|
+
viewerCanUpdateBranch
|
|
409
|
+
isMergeQueueEnabled
|
|
410
|
+
isInMergeQueue
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}`;
|
|
414
|
+
const PULL_REQUEST_TIMELINE_QUERY = `
|
|
415
|
+
query PullRequestTimeline($owner: String!, $name: String!, $number: Int!) {
|
|
416
|
+
repository(owner: $owner, name: $name) {
|
|
417
|
+
pullRequest(number: $number) {
|
|
418
|
+
number
|
|
419
|
+
reviews(first: 100) {
|
|
420
|
+
nodes {
|
|
421
|
+
id
|
|
422
|
+
state
|
|
423
|
+
body
|
|
424
|
+
bodyHTML
|
|
425
|
+
url
|
|
426
|
+
submittedAt
|
|
427
|
+
author {
|
|
428
|
+
login
|
|
429
|
+
url
|
|
430
|
+
avatarUrl
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
pageInfo {
|
|
434
|
+
hasNextPage
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
comments(first: 100) {
|
|
438
|
+
nodes {
|
|
439
|
+
id
|
|
440
|
+
body
|
|
441
|
+
bodyHTML
|
|
442
|
+
url
|
|
443
|
+
createdAt
|
|
444
|
+
author {
|
|
445
|
+
login
|
|
446
|
+
url
|
|
447
|
+
avatarUrl
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
pageInfo {
|
|
451
|
+
hasNextPage
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
reviewThreads(first: 100) {
|
|
455
|
+
nodes {
|
|
456
|
+
id
|
|
457
|
+
path
|
|
458
|
+
line
|
|
459
|
+
startLine
|
|
460
|
+
isResolved
|
|
461
|
+
isOutdated
|
|
462
|
+
comments(first: 100) {
|
|
463
|
+
nodes {
|
|
464
|
+
id
|
|
465
|
+
body
|
|
466
|
+
bodyHTML
|
|
467
|
+
url
|
|
468
|
+
createdAt
|
|
469
|
+
author {
|
|
470
|
+
login
|
|
471
|
+
url
|
|
472
|
+
avatarUrl
|
|
473
|
+
}
|
|
474
|
+
pullRequestReview {
|
|
475
|
+
id
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
pageInfo {
|
|
479
|
+
hasNextPage
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
pageInfo {
|
|
484
|
+
hasNextPage
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}`;
|
|
490
|
+
const BatchPollPrNodeSchema = z.object({
|
|
491
|
+
number: z.number(),
|
|
492
|
+
url: z.string().catch(""),
|
|
493
|
+
title: z.string().catch(""),
|
|
494
|
+
state: z.string().catch(""),
|
|
495
|
+
isDraft: z.boolean().optional().catch(false),
|
|
496
|
+
baseRefName: z.string().catch(""),
|
|
497
|
+
headRefName: z.string().catch(""),
|
|
498
|
+
headRefOid: z.string().catch(""),
|
|
499
|
+
mergedAt: z.string().nullable().optional(),
|
|
500
|
+
mergeable: PullRequestMergeableSchema.optional().default("UNKNOWN"),
|
|
501
|
+
reviewDecision: z.unknown().optional(),
|
|
502
|
+
headRepositoryOwner: HeadRepositoryOwnerSchema,
|
|
503
|
+
mergeStateStatus: z.string().nullable().optional().catch(null),
|
|
504
|
+
autoMergeRequest: GitHubAutoMergeRequestSchema,
|
|
505
|
+
viewerCanEnableAutoMerge: z.boolean().optional().catch(false),
|
|
506
|
+
viewerCanDisableAutoMerge: z.boolean().optional().catch(false),
|
|
507
|
+
viewerCanMergeAsAdmin: z.boolean().optional().catch(false),
|
|
508
|
+
viewerCanUpdateBranch: z.boolean().optional().catch(false),
|
|
509
|
+
isMergeQueueEnabled: z.boolean().optional().catch(false),
|
|
510
|
+
isInMergeQueue: z.boolean().optional().catch(false),
|
|
511
|
+
});
|
|
512
|
+
const BatchPollRepositorySchema = z.object({
|
|
513
|
+
owner: z.object({ login: z.string().optional() }).nullable().optional().catch(null),
|
|
514
|
+
isFork: z.boolean().optional().catch(false),
|
|
515
|
+
parent: z
|
|
516
|
+
.object({ owner: z.object({ login: z.string() }), name: z.string() })
|
|
517
|
+
.nullable()
|
|
518
|
+
.optional()
|
|
519
|
+
.catch(null),
|
|
520
|
+
autoMergeAllowed: z.boolean().optional().catch(false),
|
|
521
|
+
mergeCommitAllowed: z.boolean().optional().catch(false),
|
|
522
|
+
squashMergeAllowed: z.boolean().optional().catch(false),
|
|
523
|
+
rebaseMergeAllowed: z.boolean().optional().catch(false),
|
|
524
|
+
viewerDefaultMergeMethod: z.string().nullable().optional().catch(null),
|
|
525
|
+
pullRequests: z.object({ nodes: z.array(BatchPollPrNodeSchema) }),
|
|
526
|
+
});
|
|
527
|
+
const GitHubGraphqlRateLimitSchema = z.object({
|
|
528
|
+
limit: z.number(),
|
|
529
|
+
remaining: z.number(),
|
|
530
|
+
resetAt: z.string(),
|
|
531
|
+
});
|
|
532
|
+
const GitHubRateLimitResponseSchema = z.object({
|
|
533
|
+
resources: z.object({
|
|
534
|
+
graphql: z.object({
|
|
535
|
+
remaining: z.number(),
|
|
536
|
+
reset: z.number(),
|
|
537
|
+
}),
|
|
538
|
+
}),
|
|
539
|
+
});
|
|
540
|
+
const BatchChecksCommitSchema = z.object({
|
|
541
|
+
commit: z.object({
|
|
542
|
+
statusCheckRollup: z
|
|
543
|
+
.object({
|
|
544
|
+
contexts: z.object({ nodes: z.array(z.unknown()) }),
|
|
545
|
+
})
|
|
546
|
+
.nullable()
|
|
547
|
+
.optional(),
|
|
548
|
+
}),
|
|
549
|
+
});
|
|
550
|
+
const BatchChecksRepositorySchema = z.object({
|
|
551
|
+
pullRequest: z
|
|
552
|
+
.object({
|
|
553
|
+
commits: z.object({ nodes: z.array(BatchChecksCommitSchema) }),
|
|
554
|
+
})
|
|
555
|
+
.nullable(),
|
|
556
|
+
});
|
|
557
|
+
// GraphQL CheckRun nodes carry the workflow name behind
|
|
558
|
+
// checkSuite.workflowRun.workflow.name; the gh CLI flattens it to workflowName.
|
|
559
|
+
// Batch results are remapped to the CLI shape so parseStatusCheckRollup stays
|
|
560
|
+
// the single rollup parser for both paths.
|
|
561
|
+
const BatchCheckRunContextSchema = z.object({
|
|
562
|
+
__typename: z.literal("CheckRun"),
|
|
563
|
+
databaseId: z.number().nullable().optional(),
|
|
564
|
+
name: z.string(),
|
|
565
|
+
status: z.string().nullable().optional(),
|
|
566
|
+
conclusion: z.string().nullable().optional(),
|
|
567
|
+
detailsUrl: z.string().nullable().optional(),
|
|
568
|
+
startedAt: z.string().nullable().optional(),
|
|
569
|
+
completedAt: z.string().nullable().optional(),
|
|
570
|
+
checkSuite: z
|
|
571
|
+
.object({
|
|
572
|
+
workflowRun: z
|
|
573
|
+
.object({
|
|
574
|
+
databaseId: z.number().nullable().optional(),
|
|
575
|
+
workflow: z.object({ name: z.string().nullable().optional() }).nullable().optional(),
|
|
576
|
+
})
|
|
577
|
+
.nullable()
|
|
578
|
+
.optional(),
|
|
579
|
+
})
|
|
580
|
+
.nullable()
|
|
581
|
+
.optional(),
|
|
582
|
+
});
|
|
583
|
+
const BATCH_PR_STATUS_FRAGMENT = `
|
|
584
|
+
fragment PaseoPollPullRequest on PullRequest {
|
|
585
|
+
number
|
|
586
|
+
url
|
|
587
|
+
title
|
|
588
|
+
state
|
|
589
|
+
isDraft
|
|
590
|
+
baseRefName
|
|
591
|
+
headRefName
|
|
592
|
+
headRefOid
|
|
593
|
+
mergedAt
|
|
594
|
+
mergeable
|
|
595
|
+
reviewDecision
|
|
596
|
+
headRepositoryOwner {
|
|
597
|
+
login
|
|
598
|
+
}
|
|
599
|
+
mergeStateStatus
|
|
600
|
+
autoMergeRequest {
|
|
601
|
+
enabledAt
|
|
602
|
+
mergeMethod
|
|
603
|
+
enabledBy {
|
|
604
|
+
login
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
viewerCanEnableAutoMerge
|
|
608
|
+
viewerCanDisableAutoMerge
|
|
609
|
+
viewerCanMergeAsAdmin
|
|
610
|
+
viewerCanUpdateBranch
|
|
611
|
+
isMergeQueueEnabled
|
|
612
|
+
isInMergeQueue
|
|
613
|
+
}`;
|
|
614
|
+
function batchAlias(index) {
|
|
615
|
+
return `t${index}`;
|
|
616
|
+
}
|
|
617
|
+
// GraphQL string literals share JSON's escaping rules.
|
|
618
|
+
function graphqlString(value) {
|
|
619
|
+
return JSON.stringify(value);
|
|
620
|
+
}
|
|
621
|
+
function buildBatchPullRequestStatusQuery(entries) {
|
|
622
|
+
const aliases = entries.map((entry, index) => ` ${batchAlias(index)}: repository(owner: ${graphqlString(entry.owner)}, name: ${graphqlString(entry.name)}) {
|
|
623
|
+
owner {
|
|
624
|
+
login
|
|
625
|
+
}
|
|
626
|
+
isFork
|
|
627
|
+
parent {
|
|
628
|
+
owner {
|
|
629
|
+
login
|
|
630
|
+
}
|
|
631
|
+
name
|
|
632
|
+
}
|
|
633
|
+
autoMergeAllowed
|
|
634
|
+
mergeCommitAllowed
|
|
635
|
+
squashMergeAllowed
|
|
636
|
+
rebaseMergeAllowed
|
|
637
|
+
viewerDefaultMergeMethod
|
|
638
|
+
pullRequests(headRefName: ${graphqlString(entry.headRef)}, first: ${BATCH_PR_CANDIDATE_LIMIT}, orderBy: {field: CREATED_AT, direction: DESC}) {
|
|
639
|
+
nodes {
|
|
640
|
+
...PaseoPollPullRequest
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}`);
|
|
644
|
+
return `query PaseoBatchPullRequestStatus {
|
|
645
|
+
rateLimit {
|
|
646
|
+
limit
|
|
647
|
+
remaining
|
|
648
|
+
resetAt
|
|
649
|
+
}
|
|
650
|
+
${aliases.join("\n")}
|
|
651
|
+
}\n${BATCH_PR_STATUS_FRAGMENT}`;
|
|
652
|
+
}
|
|
653
|
+
function buildBatchPullRequestChecksQuery(entries) {
|
|
654
|
+
const aliases = entries.map((entry, index) => ` ${batchAlias(index)}: repository(owner: ${graphqlString(entry.owner)}, name: ${graphqlString(entry.name)}) {
|
|
655
|
+
pullRequest(number: ${entry.number}) {
|
|
656
|
+
commits(last: 1) {
|
|
657
|
+
nodes {
|
|
658
|
+
commit {
|
|
659
|
+
statusCheckRollup {
|
|
660
|
+
contexts(first: 100) {
|
|
661
|
+
nodes {
|
|
662
|
+
__typename
|
|
663
|
+
... on CheckRun {
|
|
664
|
+
databaseId
|
|
665
|
+
name
|
|
666
|
+
status
|
|
667
|
+
conclusion
|
|
668
|
+
detailsUrl
|
|
669
|
+
startedAt
|
|
670
|
+
completedAt
|
|
671
|
+
checkSuite {
|
|
672
|
+
workflowRun {
|
|
673
|
+
databaseId
|
|
674
|
+
workflow {
|
|
675
|
+
name
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
... on StatusContext {
|
|
681
|
+
context
|
|
682
|
+
state
|
|
683
|
+
targetUrl
|
|
684
|
+
createdAt
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}`);
|
|
694
|
+
return `query PaseoBatchPullRequestChecks {
|
|
695
|
+
rateLimit {
|
|
696
|
+
limit
|
|
697
|
+
remaining
|
|
698
|
+
resetAt
|
|
699
|
+
}
|
|
700
|
+
${aliases.join("\n")}
|
|
701
|
+
}`;
|
|
702
|
+
}
|
|
703
|
+
function toBatchStatusCheckRollupNode(node) {
|
|
704
|
+
const parsed = BatchCheckRunContextSchema.safeParse(node);
|
|
705
|
+
if (!parsed.success) {
|
|
706
|
+
// StatusContext nodes already match the CLI rollup shape.
|
|
707
|
+
return node;
|
|
708
|
+
}
|
|
709
|
+
const { checkSuite, ...rest } = parsed.data;
|
|
710
|
+
return {
|
|
711
|
+
...rest,
|
|
712
|
+
workflowName: checkSuite?.workflowRun?.workflow?.name ?? null,
|
|
713
|
+
checkSuite: { workflowRun: { databaseId: checkSuite?.workflowRun?.databaseId ?? null } },
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function extractGraphqlBatchAliases(body) {
|
|
717
|
+
if (!body || typeof body !== "object") {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
const data = body.data;
|
|
721
|
+
if (!data || typeof data !== "object") {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
return data;
|
|
725
|
+
}
|
|
726
|
+
function parseGraphqlBatchAliases(stdout) {
|
|
727
|
+
let parsed;
|
|
728
|
+
try {
|
|
729
|
+
parsed = JSON.parse(stdout || "{}");
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
return {};
|
|
733
|
+
}
|
|
734
|
+
return extractGraphqlBatchAliases(parsed) ?? {};
|
|
735
|
+
}
|
|
736
|
+
// gh exits non-zero when any aliased sub-query errors but still prints the
|
|
737
|
+
// response body, so the healthy aliases in a batch are recoverable.
|
|
738
|
+
function extractGraphqlBatchAliasesFromError(error) {
|
|
739
|
+
if (!(error instanceof GitHubCommandError) || !error.stdout) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
return extractGraphqlBatchAliases(JSON.parse(error.stdout));
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function parseGraphqlBatchRateLimit(stdout) {
|
|
750
|
+
try {
|
|
751
|
+
const aliases = extractGraphqlBatchAliases(JSON.parse(stdout));
|
|
752
|
+
const parsed = GitHubGraphqlRateLimitSchema.safeParse(aliases?.rateLimit);
|
|
753
|
+
return parsed.success ? parsed.data : null;
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const DIRECT_PULL_REQUEST_MERGE_STATE_ALLOWLIST = new Set(["CLEAN", "HAS_HOOKS"]);
|
|
760
|
+
export class GitHubCliMissingError extends ForgeCliMissingError {
|
|
761
|
+
constructor() {
|
|
762
|
+
super("GitHub CLI (gh) is not installed or not in PATH");
|
|
763
|
+
this.name = "GitHubCliMissingError";
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
export class GitHubAuthenticationError extends ForgeAuthenticationError {
|
|
767
|
+
constructor(params) {
|
|
768
|
+
super("GitHub CLI authentication failed", params);
|
|
769
|
+
this.name = "GitHubAuthenticationError";
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
export class GitHubCommandError extends ForgeCommandError {
|
|
773
|
+
constructor(params) {
|
|
774
|
+
super({ brand: "GitHub", binary: "gh" }, params);
|
|
775
|
+
this.name = "GitHubCommandError";
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
class GitHubGraphqlPollPausedError extends Error {
|
|
779
|
+
constructor(params) {
|
|
780
|
+
super(`GitHub GraphQL polling paused until ${params.resetAt}`);
|
|
781
|
+
this.name = "GitHubGraphqlPollPausedError";
|
|
782
|
+
this.remaining = params.remaining;
|
|
783
|
+
this.resetAt = params.resetAt;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
export class GitHubEnterpriseHostProbeError extends Error {
|
|
787
|
+
constructor(params) {
|
|
788
|
+
super(`Unable to verify GitHub Enterprise host ${params.host}`);
|
|
789
|
+
this.name = "GitHubEnterpriseHostProbeError";
|
|
790
|
+
this.host = params.host;
|
|
791
|
+
this.cause = params.cause;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
export function createGitHubService(options = {}) {
|
|
795
|
+
const ttlMs = options.ttlMs ?? DEFAULT_GITHUB_CACHE_TTL_MS;
|
|
796
|
+
const deps = {
|
|
797
|
+
runner: options.runner ?? runGhCommand,
|
|
798
|
+
resolveGhPath: options.resolveGhPath ?? resolveGhPath,
|
|
799
|
+
now: options.now ?? Date.now,
|
|
800
|
+
resolveRepoHost: options.resolveRepoHost ?? resolveGitHubEnterpriseHost,
|
|
801
|
+
resolveRepoSlug: options.resolveRepoSlug ?? resolveGitHubSlugFromOrigin,
|
|
802
|
+
};
|
|
803
|
+
// A resolved enterprise host is cached permanently; a null resolution (no
|
|
804
|
+
// host, or the auth probe said no) expires so `gh auth login --hostname`
|
|
805
|
+
// run after the first probe is picked up without a daemon restart. The
|
|
806
|
+
// `resolved` marker survives a TTL refresh so poll batching can keep using
|
|
807
|
+
// the last known host synchronously while the refresh is in flight.
|
|
808
|
+
const repoHostByCwd = new Map();
|
|
809
|
+
// owner/name from the origin remote, cached until invalidate() (which fires
|
|
810
|
+
// on remote changes) so batched polls can group targets synchronously. Null
|
|
811
|
+
// resolutions expire like the host cache's so transient git failures don't
|
|
812
|
+
// permanently degrade a cwd to per-target polling.
|
|
813
|
+
const repoSlugByCwd = new Map();
|
|
814
|
+
const cache = new Map();
|
|
815
|
+
const inFlight = new Map();
|
|
816
|
+
const pollTargets = new Map();
|
|
817
|
+
const checkLogTailCache = new Map();
|
|
818
|
+
// Rollup nodes for merged/closed PRs keyed by repo#number@headOid: their
|
|
819
|
+
// checks can no longer change, so each is fetched at most once per daemon run.
|
|
820
|
+
const frozenPullRequestChecksCache = new Map();
|
|
821
|
+
const pollRepositoryRedirects = new Map();
|
|
822
|
+
const pollPausedUntilByHost = new Map();
|
|
823
|
+
const rateLimitResetLoads = new Map();
|
|
824
|
+
let githubPollTimer = null;
|
|
825
|
+
let api;
|
|
826
|
+
async function cached(params) {
|
|
827
|
+
if (params.readOptions?.force && !params.readOptions.reason) {
|
|
828
|
+
throw new Error("ForgeService forced read requires a reason");
|
|
829
|
+
}
|
|
830
|
+
const key = buildCacheKey({
|
|
831
|
+
cwd: params.cwd,
|
|
832
|
+
method: params.method,
|
|
833
|
+
args: params.args,
|
|
834
|
+
});
|
|
835
|
+
const cachedEntry = cache.get(key);
|
|
836
|
+
const now = deps.now();
|
|
837
|
+
if (!params.readOptions?.force && cachedEntry && cachedEntry.expiresAt > now) {
|
|
838
|
+
return cachedEntry.value;
|
|
839
|
+
}
|
|
840
|
+
const existing = inFlight.get(key);
|
|
841
|
+
if (existing && (!params.readOptions?.force || existing.force)) {
|
|
842
|
+
return existing.promise;
|
|
843
|
+
}
|
|
844
|
+
const request = params
|
|
845
|
+
.load()
|
|
846
|
+
.then((value) => {
|
|
847
|
+
if (inFlight.get(key)?.promise === request) {
|
|
848
|
+
cache.set(key, {
|
|
849
|
+
value,
|
|
850
|
+
cwd: params.cwd,
|
|
851
|
+
expiresAt: deps.now() + ttlMs,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
return value;
|
|
855
|
+
})
|
|
856
|
+
.finally(() => {
|
|
857
|
+
if (inFlight.get(key)?.promise === request) {
|
|
858
|
+
inFlight.delete(key);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
inFlight.set(key, {
|
|
862
|
+
cwd: params.cwd,
|
|
863
|
+
promise: request,
|
|
864
|
+
force: params.readOptions?.force === true,
|
|
865
|
+
});
|
|
866
|
+
return request;
|
|
867
|
+
}
|
|
868
|
+
function resolveRepoHostCached(cwd) {
|
|
869
|
+
const cachedHost = repoHostByCwd.get(cwd);
|
|
870
|
+
if (cachedHost && (cachedHost.expiresAt === null || deps.now() < cachedHost.expiresAt)) {
|
|
871
|
+
return cachedHost.promise;
|
|
872
|
+
}
|
|
873
|
+
const pending = deps
|
|
874
|
+
.resolveRepoHost(cwd)
|
|
875
|
+
.then((host) => {
|
|
876
|
+
const current = repoHostByCwd.get(cwd);
|
|
877
|
+
if (current?.promise === pending) {
|
|
878
|
+
current.resolved = { value: host };
|
|
879
|
+
if (host === null) {
|
|
880
|
+
current.expiresAt = deps.now() + REPO_HOST_NULL_TTL_MS;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return host;
|
|
884
|
+
})
|
|
885
|
+
.catch((error) => {
|
|
886
|
+
if (repoHostByCwd.get(cwd)?.promise === pending) {
|
|
887
|
+
repoHostByCwd.delete(cwd);
|
|
888
|
+
}
|
|
889
|
+
throw error;
|
|
890
|
+
});
|
|
891
|
+
repoHostByCwd.set(cwd, {
|
|
892
|
+
promise: pending,
|
|
893
|
+
expiresAt: null,
|
|
894
|
+
...(cachedHost?.resolved ? { resolved: cachedHost.resolved } : {}),
|
|
895
|
+
});
|
|
896
|
+
return pending;
|
|
897
|
+
}
|
|
898
|
+
function peekRepoHost(cwd) {
|
|
899
|
+
const entry = repoHostByCwd.get(cwd);
|
|
900
|
+
if (!entry) {
|
|
901
|
+
void resolveRepoHostCached(cwd).catch(() => { });
|
|
902
|
+
return { settled: false, value: null };
|
|
903
|
+
}
|
|
904
|
+
return entry.resolved
|
|
905
|
+
? { settled: true, value: entry.resolved.value }
|
|
906
|
+
: { settled: false, value: null };
|
|
907
|
+
}
|
|
908
|
+
function resolveRepoSlugCached(cwd) {
|
|
909
|
+
const cachedSlug = repoSlugByCwd.get(cwd);
|
|
910
|
+
if (cachedSlug && (cachedSlug.expiresAt === null || deps.now() < cachedSlug.expiresAt)) {
|
|
911
|
+
return cachedSlug.promise;
|
|
912
|
+
}
|
|
913
|
+
// A null slug expires: resolveRepoSlug also returns null for transient git
|
|
914
|
+
// failures (lock contention, timeout), and a permanent null would silently
|
|
915
|
+
// pin this cwd to the legacy per-target poll path. The `resolved` marker
|
|
916
|
+
// survives the refresh so batching keeps the last known slug meanwhile.
|
|
917
|
+
const entry = {
|
|
918
|
+
promise: deps
|
|
919
|
+
.resolveRepoSlug(cwd)
|
|
920
|
+
.then((slug) => {
|
|
921
|
+
if (repoSlugByCwd.get(cwd) === entry) {
|
|
922
|
+
entry.resolved = { value: slug };
|
|
923
|
+
if (slug === null) {
|
|
924
|
+
entry.expiresAt = deps.now() + REPO_HOST_NULL_TTL_MS;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return slug;
|
|
928
|
+
})
|
|
929
|
+
.catch(() => {
|
|
930
|
+
if (repoSlugByCwd.get(cwd) === entry) {
|
|
931
|
+
entry.resolved = { value: null };
|
|
932
|
+
entry.expiresAt = deps.now() + REPO_HOST_NULL_TTL_MS;
|
|
933
|
+
}
|
|
934
|
+
return null;
|
|
935
|
+
}),
|
|
936
|
+
expiresAt: null,
|
|
937
|
+
...(cachedSlug?.resolved ? { resolved: cachedSlug.resolved } : {}),
|
|
938
|
+
};
|
|
939
|
+
repoSlugByCwd.set(cwd, entry);
|
|
940
|
+
return entry.promise;
|
|
941
|
+
}
|
|
942
|
+
function peekRepoSlug(cwd) {
|
|
943
|
+
const entry = repoSlugByCwd.get(cwd);
|
|
944
|
+
if (!entry) {
|
|
945
|
+
void resolveRepoSlugCached(cwd);
|
|
946
|
+
return { settled: false, value: null };
|
|
947
|
+
}
|
|
948
|
+
return entry.resolved
|
|
949
|
+
? { settled: true, value: entry.resolved.value }
|
|
950
|
+
: { settled: false, value: null };
|
|
951
|
+
}
|
|
952
|
+
async function run(args, runOptions) {
|
|
953
|
+
const ghPath = await deps.resolveGhPath();
|
|
954
|
+
if (!ghPath) {
|
|
955
|
+
throw new GitHubCliMissingError();
|
|
956
|
+
}
|
|
957
|
+
// Route every gh invocation to the workspace's host. `gh api`/`graphql`
|
|
958
|
+
// otherwise default to github.com regardless of the resolved repository,
|
|
959
|
+
// which silently queries the wrong server on GitHub Enterprise. GH_HOST is
|
|
960
|
+
// safe even for auto-routing subcommands because it matches the repo's host.
|
|
961
|
+
const host = await resolveRepoHostCached(runOptions.cwd);
|
|
962
|
+
const effectiveOptions = host
|
|
963
|
+
? { ...runOptions, envOverlay: { ...runOptions.envOverlay, GH_HOST: host } }
|
|
964
|
+
: runOptions;
|
|
965
|
+
try {
|
|
966
|
+
const result = await deps.runner(args, effectiveOptions);
|
|
967
|
+
return result.stdout.trim();
|
|
968
|
+
}
|
|
969
|
+
catch (error) {
|
|
970
|
+
throw githubCliRunner.normalizeError(error, {
|
|
971
|
+
args,
|
|
972
|
+
cwd: runOptions.cwd,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
async function runGhJson(args, runOptions, schema, emptyFallback) {
|
|
977
|
+
const stdout = await run(args, runOptions);
|
|
978
|
+
return parseGitHubJsonOutput(stdout, schema, {
|
|
979
|
+
args,
|
|
980
|
+
cwd: runOptions.cwd,
|
|
981
|
+
emptyFallback,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function getPollTargetKey(target) {
|
|
985
|
+
return buildCacheKey({
|
|
986
|
+
cwd: target.cwd,
|
|
987
|
+
method: "getCurrentPullRequestStatus",
|
|
988
|
+
args: {
|
|
989
|
+
headRef: target.headRef,
|
|
990
|
+
headSha: target.headSha,
|
|
991
|
+
headRepositoryOwner: target.headRepositoryOwner,
|
|
992
|
+
},
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
function updatePollTargetAfterSuccess(update) {
|
|
996
|
+
const target = pollTargets.get(getPollTargetKey(update));
|
|
997
|
+
if (!target) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
target.latestStatus = update.status;
|
|
1001
|
+
target.consecutiveErrors = 0;
|
|
1002
|
+
if (update.notify) {
|
|
1003
|
+
for (const callback of target.callbacks) {
|
|
1004
|
+
callback(update.status);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
scheduleGitHubPoll(target);
|
|
1008
|
+
}
|
|
1009
|
+
function scheduleGitHubPoll(target) {
|
|
1010
|
+
scheduleGitHubPollAfter(target, computeGithubNextInterval(target.latestStatus, target.consecutiveErrors));
|
|
1011
|
+
}
|
|
1012
|
+
function scheduleImmediateGitHubPoll(target) {
|
|
1013
|
+
scheduleGitHubPollAfter(target, 0);
|
|
1014
|
+
}
|
|
1015
|
+
function scheduleGitHubPollAfter(target, delayMs) {
|
|
1016
|
+
if (target.retainCount <= 0) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const now = deps.now();
|
|
1020
|
+
const baseline = target.pollCycleStartedAt ?? now;
|
|
1021
|
+
target.pollCycleStartedAt = null;
|
|
1022
|
+
const pauseUntil = getPollPauseUntil(target.cwd);
|
|
1023
|
+
if (delayMs <= 0) {
|
|
1024
|
+
target.nextDueAt = Math.max(now, pauseUntil);
|
|
1025
|
+
armGitHubPollTimer();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
// Non-immediate polls snap to a shared time grid so targets converge on the
|
|
1029
|
+
// same tick and one batched GraphQL request covers all of them. The next
|
|
1030
|
+
// cycle anchors at the previous cycle's due time so slow requests don't
|
|
1031
|
+
// drift the grid — but when the cycle overran its slot entirely, re-anchor
|
|
1032
|
+
// at completion: scheduling in the past would poll back-to-back precisely
|
|
1033
|
+
// when GitHub is slow or erroring.
|
|
1034
|
+
const dueAt = alignGitHubPollTime(baseline + delayMs);
|
|
1035
|
+
const nextDueAt = dueAt >= now ? dueAt : alignGitHubPollTime(now + delayMs);
|
|
1036
|
+
target.nextDueAt = Math.max(nextDueAt, pauseUntil);
|
|
1037
|
+
armGitHubPollTimer();
|
|
1038
|
+
}
|
|
1039
|
+
function getPollPauseUntil(cwd) {
|
|
1040
|
+
const host = peekRepoHost(cwd);
|
|
1041
|
+
if (!host.settled) {
|
|
1042
|
+
return 0;
|
|
1043
|
+
}
|
|
1044
|
+
const key = host.value ?? "";
|
|
1045
|
+
const pauseUntil = pollPausedUntilByHost.get(key) ?? 0;
|
|
1046
|
+
if (pauseUntil <= deps.now()) {
|
|
1047
|
+
pollPausedUntilByHost.delete(key);
|
|
1048
|
+
return 0;
|
|
1049
|
+
}
|
|
1050
|
+
return pauseUntil;
|
|
1051
|
+
}
|
|
1052
|
+
function alignGitHubPollTime(dueAt) {
|
|
1053
|
+
return Math.ceil(dueAt / GITHUB_POLL_ALIGNMENT_MS) * GITHUB_POLL_ALIGNMENT_MS;
|
|
1054
|
+
}
|
|
1055
|
+
function armGitHubPollTimer() {
|
|
1056
|
+
let earliest = null;
|
|
1057
|
+
for (const target of pollTargets.values()) {
|
|
1058
|
+
if (target.retainCount <= 0 || target.nextDueAt === null) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (earliest === null || target.nextDueAt < earliest) {
|
|
1062
|
+
earliest = target.nextDueAt;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (githubPollTimer) {
|
|
1066
|
+
clearTimeout(githubPollTimer);
|
|
1067
|
+
githubPollTimer = null;
|
|
1068
|
+
}
|
|
1069
|
+
if (earliest === null) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
githubPollTimer = setTimeout(() => {
|
|
1073
|
+
githubPollTimer = null;
|
|
1074
|
+
void flushDueGitHubPolls();
|
|
1075
|
+
}, Math.max(0, earliest - deps.now()));
|
|
1076
|
+
}
|
|
1077
|
+
async function flushDueGitHubPolls() {
|
|
1078
|
+
const now = deps.now();
|
|
1079
|
+
const due = [];
|
|
1080
|
+
for (const target of pollTargets.values()) {
|
|
1081
|
+
if (target.retainCount > 0 && target.nextDueAt !== null && target.nextDueAt <= now) {
|
|
1082
|
+
target.nextDueAt = null;
|
|
1083
|
+
target.pollCycleStartedAt = now;
|
|
1084
|
+
due.push(target);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
armGitHubPollTimer();
|
|
1088
|
+
if (due.length === 0) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const legacyTargets = [];
|
|
1092
|
+
const batchGroups = new Map();
|
|
1093
|
+
for (const target of due) {
|
|
1094
|
+
if (target.retainCount <= 0) {
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
const host = peekRepoHost(target.cwd);
|
|
1098
|
+
const slug = peekRepoSlug(target.cwd);
|
|
1099
|
+
if (!host.settled || !slug.settled) {
|
|
1100
|
+
target.pollCycleStartedAt = null;
|
|
1101
|
+
void Promise.all([
|
|
1102
|
+
resolveRepoHostCached(target.cwd).catch(() => null),
|
|
1103
|
+
resolveRepoSlugCached(target.cwd),
|
|
1104
|
+
]).then(() => {
|
|
1105
|
+
if (target.retainCount > 0 && target.nextDueAt === null) {
|
|
1106
|
+
// A non-zero delay rejoins the shared polling grid.
|
|
1107
|
+
scheduleGitHubPollAfter(target, 1);
|
|
1108
|
+
}
|
|
1109
|
+
return undefined;
|
|
1110
|
+
});
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
const [owner, name, extra] = slug.value?.split("/") ?? [];
|
|
1114
|
+
// A cwd with no origin slug polls through the legacy per-target path;
|
|
1115
|
+
// batch grouping needs a complete owner/name identity.
|
|
1116
|
+
if (!owner || !name || extra !== undefined) {
|
|
1117
|
+
legacyTargets.push(target);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
const key = host.value ?? "";
|
|
1121
|
+
const group = batchGroups.get(key) ?? [];
|
|
1122
|
+
group.push({
|
|
1123
|
+
target,
|
|
1124
|
+
host: key,
|
|
1125
|
+
originOwner: owner,
|
|
1126
|
+
originName: name,
|
|
1127
|
+
owner,
|
|
1128
|
+
name,
|
|
1129
|
+
});
|
|
1130
|
+
batchGroups.set(key, group);
|
|
1131
|
+
}
|
|
1132
|
+
await Promise.all([
|
|
1133
|
+
...legacyTargets.map((target) => runGitHubPoll(target)),
|
|
1134
|
+
...[...batchGroups.values()].map((group) => runGitHubPollGroup(group, now)),
|
|
1135
|
+
]);
|
|
1136
|
+
}
|
|
1137
|
+
async function runGitHubPollGroup(entries, startedAt) {
|
|
1138
|
+
for (let start = 0; start < entries.length; start += GITHUB_POLL_BATCH_MAX) {
|
|
1139
|
+
const chunk = entries.slice(start, start + GITHUB_POLL_BATCH_MAX);
|
|
1140
|
+
if ((pollPausedUntilByHost.get(chunk[0]?.host ?? "") ?? 0) > deps.now()) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
await runGitHubPollBatch(chunk, startedAt);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function pauseGitHubPollsAtReserve(host, rateLimit) {
|
|
1147
|
+
if (rateLimit.remaining > rateLimit.limit * GITHUB_GRAPHQL_RESERVE_RATIO) {
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
const resetAt = Date.parse(rateLimit.resetAt);
|
|
1151
|
+
if (!Number.isFinite(resetAt)) {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
pauseGitHubPollHostUntil(host, resetAt + GITHUB_GRAPHQL_RESET_GRACE_MS);
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
1157
|
+
function pauseGitHubPollHostUntil(host, pauseUntil) {
|
|
1158
|
+
if (pauseUntil <= deps.now()) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const currentPause = pollPausedUntilByHost.get(host) ?? 0;
|
|
1162
|
+
pollPausedUntilByHost.set(host, Math.max(currentPause, pauseUntil));
|
|
1163
|
+
for (const target of pollTargets.values()) {
|
|
1164
|
+
const targetHost = peekRepoHost(target.cwd);
|
|
1165
|
+
if (!targetHost.settled || (targetHost.value ?? "") !== host) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
target.pollCycleStartedAt = null;
|
|
1169
|
+
target.nextDueAt = Math.max(target.nextDueAt ?? 0, pauseUntil);
|
|
1170
|
+
}
|
|
1171
|
+
armGitHubPollTimer();
|
|
1172
|
+
}
|
|
1173
|
+
async function pauseGitHubPollsAfterRateLimit(entry, error) {
|
|
1174
|
+
if (!isGitHubRateLimitError(error)) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
const resetAt = await loadGitHubGraphqlResetAt(entry);
|
|
1178
|
+
if (resetAt === null) {
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
pauseGitHubPollHostUntil(entry.host, resetAt + GITHUB_GRAPHQL_RESET_GRACE_MS);
|
|
1182
|
+
return true;
|
|
1183
|
+
}
|
|
1184
|
+
function loadGitHubGraphqlResetAt(entry) {
|
|
1185
|
+
const existing = rateLimitResetLoads.get(entry.host);
|
|
1186
|
+
if (existing) {
|
|
1187
|
+
return existing;
|
|
1188
|
+
}
|
|
1189
|
+
const pending = runGhJson(["api", "rate_limit"], { cwd: entry.target.cwd }, GitHubRateLimitResponseSchema, "{}")
|
|
1190
|
+
.then((response) => response.resources.graphql.reset * 1000)
|
|
1191
|
+
.catch(() => null)
|
|
1192
|
+
.finally(() => {
|
|
1193
|
+
if (rateLimitResetLoads.get(entry.host) === pending) {
|
|
1194
|
+
rateLimitResetLoads.delete(entry.host);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
rateLimitResetLoads.set(entry.host, pending);
|
|
1198
|
+
return pending;
|
|
1199
|
+
}
|
|
1200
|
+
async function runGitHubPollBatch(entries, startedAt) {
|
|
1201
|
+
const addressedEntries = entries.map(addressGitHubBatchEntry);
|
|
1202
|
+
const firstEntry = addressedEntries[0];
|
|
1203
|
+
if (!firstEntry) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const query = buildBatchPullRequestStatusQuery(addressedEntries.map((entry) => ({
|
|
1207
|
+
owner: entry.owner,
|
|
1208
|
+
name: entry.name,
|
|
1209
|
+
headRef: entry.target.headRef,
|
|
1210
|
+
})));
|
|
1211
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
1212
|
+
const response = await loadGitHubPollBatchResponse({
|
|
1213
|
+
entries: addressedEntries,
|
|
1214
|
+
firstEntry,
|
|
1215
|
+
args,
|
|
1216
|
+
});
|
|
1217
|
+
if (!response) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const rateLimit = parseGraphqlBatchRateLimit(response.stdout);
|
|
1221
|
+
if (rateLimit && pauseGitHubPollsAtReserve(firstEntry.host, rateLimit)) {
|
|
1222
|
+
const error = new GitHubGraphqlPollPausedError(rateLimit);
|
|
1223
|
+
reportGitHubBatchError(addressedEntries, error);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const distribution = distributeGitHubPollBatch({
|
|
1227
|
+
entries: addressedEntries,
|
|
1228
|
+
aliases: response.aliases,
|
|
1229
|
+
args,
|
|
1230
|
+
startedAt,
|
|
1231
|
+
});
|
|
1232
|
+
if (distribution.pendingChecks.length > 0) {
|
|
1233
|
+
await loadBatchPullRequestChecks(distribution.pendingChecks, startedAt);
|
|
1234
|
+
}
|
|
1235
|
+
await Promise.all(distribution.legacyFallback.map((target) => runGitHubPoll(target)));
|
|
1236
|
+
if (distribution.redirected.length > 0) {
|
|
1237
|
+
await runGitHubPollBatch(distribution.redirected, startedAt);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
async function loadGitHubPollBatchResponse(input) {
|
|
1241
|
+
try {
|
|
1242
|
+
const stdout = await run(input.args, { cwd: input.firstEntry.target.cwd });
|
|
1243
|
+
return { aliases: parseGraphqlBatchAliases(stdout), stdout };
|
|
1244
|
+
}
|
|
1245
|
+
catch (error) {
|
|
1246
|
+
const isPaused = await pauseGitHubPollsAfterRateLimit(input.firstEntry, error);
|
|
1247
|
+
const partial = extractGraphqlBatchAliasesFromError(error);
|
|
1248
|
+
if (isPaused || !partial) {
|
|
1249
|
+
reportGitHubBatchError(input.entries, error);
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
const stdout = error instanceof GitHubCommandError ? (error.stdout ?? "") : "";
|
|
1253
|
+
return { aliases: partial, stdout };
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function reportGitHubBatchError(entries, error) {
|
|
1257
|
+
for (const entry of entries) {
|
|
1258
|
+
updatePollTargetAfterError(entry.target, error);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function distributeGitHubPollBatch(input) {
|
|
1262
|
+
const distribution = {
|
|
1263
|
+
pendingChecks: [],
|
|
1264
|
+
legacyFallback: [],
|
|
1265
|
+
redirected: [],
|
|
1266
|
+
};
|
|
1267
|
+
for (const [index, entry] of input.entries.entries()) {
|
|
1268
|
+
const aliasValue = input.aliases[batchAlias(index)];
|
|
1269
|
+
const repository = aliasValue ? BatchPollRepositorySchema.safeParse(aliasValue) : null;
|
|
1270
|
+
if (!repository?.success) {
|
|
1271
|
+
updatePollTargetAfterError(entry.target, new GitHubCommandError({
|
|
1272
|
+
args: input.args,
|
|
1273
|
+
cwd: entry.target.cwd,
|
|
1274
|
+
exitCode: null,
|
|
1275
|
+
stderr: `GitHub batch PR status response is missing repository ${entry.owner}/${entry.name}`,
|
|
1276
|
+
}));
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (repository.data.isFork && repository.data.parent) {
|
|
1280
|
+
const forkOwner = repository.data.owner?.login ?? entry.owner;
|
|
1281
|
+
pollRepositoryRedirects.set(batchRepositoryRedirectKey(entry), {
|
|
1282
|
+
owner: repository.data.parent.owner.login,
|
|
1283
|
+
name: repository.data.parent.name,
|
|
1284
|
+
headRepositoryOwner: forkOwner,
|
|
1285
|
+
});
|
|
1286
|
+
distribution.redirected.push(entry);
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
const node = selectBatchPollNode(entry, repository.data);
|
|
1290
|
+
if (!node) {
|
|
1291
|
+
// Legacy fallback covers the two ways "no match" can be wrong: a fork
|
|
1292
|
+
// whose PR lives in the parent repository, and a full candidate page —
|
|
1293
|
+
// ten newer same-named fork PRs can crowd the checkout's own PR out of
|
|
1294
|
+
// the first:N window, so absence is only conclusive on a partial page.
|
|
1295
|
+
if (repository.data.isFork ||
|
|
1296
|
+
repository.data.pullRequests.nodes.length >= BATCH_PR_CANDIDATE_LIMIT) {
|
|
1297
|
+
distribution.legacyFallback.push(entry.target);
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
finalizeBatchPollTarget({
|
|
1301
|
+
entry,
|
|
1302
|
+
node: null,
|
|
1303
|
+
repository: repository.data,
|
|
1304
|
+
rollup: null,
|
|
1305
|
+
startedAt: input.startedAt,
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const frozenKey = node.state !== "OPEN" && node.headRefOid.length > 0
|
|
1311
|
+
? `${entry.owner}/${entry.name}#${node.number}@${node.headRefOid}`
|
|
1312
|
+
: null;
|
|
1313
|
+
const cachedRollup = frozenKey ? frozenPullRequestChecksCache.get(frozenKey) : undefined;
|
|
1314
|
+
if (frozenKey && cachedRollup) {
|
|
1315
|
+
rememberFrozenPullRequestChecks(frozenKey, cachedRollup);
|
|
1316
|
+
finalizeBatchPollTarget({
|
|
1317
|
+
entry,
|
|
1318
|
+
node,
|
|
1319
|
+
repository: repository.data,
|
|
1320
|
+
rollup: cachedRollup,
|
|
1321
|
+
startedAt: input.startedAt,
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
distribution.pendingChecks.push({
|
|
1326
|
+
entry,
|
|
1327
|
+
node,
|
|
1328
|
+
repository: repository.data,
|
|
1329
|
+
cacheKey: frozenKey,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return distribution;
|
|
1334
|
+
}
|
|
1335
|
+
function addressGitHubBatchEntry(entry) {
|
|
1336
|
+
const redirect = pollRepositoryRedirects.get(batchRepositoryRedirectKey(entry));
|
|
1337
|
+
return redirect
|
|
1338
|
+
? {
|
|
1339
|
+
...entry,
|
|
1340
|
+
owner: redirect.owner,
|
|
1341
|
+
name: redirect.name,
|
|
1342
|
+
headRepositoryOwner: redirect.headRepositoryOwner,
|
|
1343
|
+
}
|
|
1344
|
+
: entry;
|
|
1345
|
+
}
|
|
1346
|
+
function batchRepositoryRedirectKey(entry) {
|
|
1347
|
+
return `${entry.host}\n${entry.originOwner}/${entry.originName}`;
|
|
1348
|
+
}
|
|
1349
|
+
function selectBatchPollNode(entry, repository) {
|
|
1350
|
+
// Prefer the canonical owner from the response: the origin slug can carry
|
|
1351
|
+
// stale casing or a pre-transfer owner that repository() redirects from.
|
|
1352
|
+
const selfOwner = (repository.owner?.login ?? entry.owner).toLowerCase();
|
|
1353
|
+
const headRepositoryOwner = entry.target.headRepositoryOwner ?? entry.headRepositoryOwner;
|
|
1354
|
+
const nodeByCandidate = new Map();
|
|
1355
|
+
const candidates = [];
|
|
1356
|
+
for (const node of repository.pullRequests.nodes) {
|
|
1357
|
+
const candidate = toCurrentPullRequestCandidate(toBatchCurrentPullRequestItem(node, undefined), entry.target.headRef);
|
|
1358
|
+
if (!candidate) {
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
// pullRequests(headRefName:) also matches forks whose branch shares the
|
|
1362
|
+
// name. Without a fork-owner hint the workspace branch lives in this
|
|
1363
|
+
// repository, so only self-owned heads are real candidates (mirrors
|
|
1364
|
+
// `gh pr view` resolving the current branch's own PR).
|
|
1365
|
+
if (!headRepositoryOwner && candidate.headRepositoryOwner?.toLowerCase() !== selfOwner) {
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
nodeByCandidate.set(candidate, node);
|
|
1369
|
+
candidates.push(candidate);
|
|
1370
|
+
}
|
|
1371
|
+
const match = pickPullRequestCandidate({
|
|
1372
|
+
candidates,
|
|
1373
|
+
headRef: entry.target.headRef,
|
|
1374
|
+
headSha: entry.target.headSha,
|
|
1375
|
+
headRepositoryOwner,
|
|
1376
|
+
});
|
|
1377
|
+
return match ? (nodeByCandidate.get(match) ?? null) : null;
|
|
1378
|
+
}
|
|
1379
|
+
async function loadBatchPullRequestChecks(pending, startedAt) {
|
|
1380
|
+
const firstItem = pending[0];
|
|
1381
|
+
if (!firstItem) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const query = buildBatchPullRequestChecksQuery(pending.map((item) => ({
|
|
1385
|
+
owner: item.entry.owner,
|
|
1386
|
+
name: item.entry.name,
|
|
1387
|
+
number: item.node.number,
|
|
1388
|
+
})));
|
|
1389
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
1390
|
+
let aliasData;
|
|
1391
|
+
let responseStdout;
|
|
1392
|
+
try {
|
|
1393
|
+
responseStdout = await run(args, { cwd: firstItem.entry.target.cwd });
|
|
1394
|
+
aliasData = parseGraphqlBatchAliases(responseStdout);
|
|
1395
|
+
}
|
|
1396
|
+
catch (error) {
|
|
1397
|
+
const isPaused = await pauseGitHubPollsAfterRateLimit(firstItem.entry, error);
|
|
1398
|
+
if (isPaused) {
|
|
1399
|
+
for (const item of pending) {
|
|
1400
|
+
updatePollTargetAfterError(item.entry.target, error);
|
|
1401
|
+
}
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
const partial = extractGraphqlBatchAliasesFromError(error);
|
|
1405
|
+
if (!partial) {
|
|
1406
|
+
for (const item of pending) {
|
|
1407
|
+
updatePollTargetAfterError(item.entry.target, error);
|
|
1408
|
+
}
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
aliasData = partial;
|
|
1412
|
+
responseStdout = error instanceof GitHubCommandError ? (error.stdout ?? "") : "";
|
|
1413
|
+
}
|
|
1414
|
+
const rateLimit = parseGraphqlBatchRateLimit(responseStdout);
|
|
1415
|
+
if (rateLimit) {
|
|
1416
|
+
pauseGitHubPollsAtReserve(firstItem.entry.host, rateLimit);
|
|
1417
|
+
}
|
|
1418
|
+
for (const [index, item] of pending.entries()) {
|
|
1419
|
+
const aliasValue = aliasData[batchAlias(index)];
|
|
1420
|
+
const parsed = aliasValue ? BatchChecksRepositorySchema.safeParse(aliasValue) : null;
|
|
1421
|
+
if (!parsed?.success || !parsed.data.pullRequest) {
|
|
1422
|
+
updatePollTargetAfterError(item.entry.target, new GitHubCommandError({
|
|
1423
|
+
args,
|
|
1424
|
+
cwd: item.entry.target.cwd,
|
|
1425
|
+
exitCode: null,
|
|
1426
|
+
stderr: `GitHub batch PR checks response is missing ${item.entry.owner}/${item.entry.name}#${item.node.number}`,
|
|
1427
|
+
}));
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
const contexts = parsed.data.pullRequest.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
|
|
1431
|
+
const rollup = contexts.map(toBatchStatusCheckRollupNode);
|
|
1432
|
+
// Freeze only fully terminal rollups: a PR closed while CI is running
|
|
1433
|
+
// would otherwise pin "pending" checks (and the fast poll cadence) for
|
|
1434
|
+
// the daemon's lifetime. Reruns that flip a terminal check back to
|
|
1435
|
+
// pending on the same SHA are deliberately not tracked — stale terminal
|
|
1436
|
+
// checks on a closed/merged PR are cosmetic.
|
|
1437
|
+
const hasPendingChecks = parseStatusCheckRollup(rollup).some((check) => check.status === "pending");
|
|
1438
|
+
if (item.cacheKey && !hasPendingChecks) {
|
|
1439
|
+
rememberFrozenPullRequestChecks(item.cacheKey, rollup);
|
|
1440
|
+
}
|
|
1441
|
+
finalizeBatchPollTarget({
|
|
1442
|
+
entry: item.entry,
|
|
1443
|
+
node: item.node,
|
|
1444
|
+
repository: item.repository,
|
|
1445
|
+
rollup,
|
|
1446
|
+
startedAt,
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function rememberFrozenPullRequestChecks(key, rollup) {
|
|
1451
|
+
frozenPullRequestChecksCache.delete(key);
|
|
1452
|
+
frozenPullRequestChecksCache.set(key, rollup);
|
|
1453
|
+
while (frozenPullRequestChecksCache.size > FROZEN_PR_CHECKS_CACHE_MAX_ENTRIES) {
|
|
1454
|
+
const oldestKey = frozenPullRequestChecksCache.keys().next().value;
|
|
1455
|
+
if (!oldestKey) {
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
frozenPullRequestChecksCache.delete(oldestKey);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function finalizeBatchPollTarget(input) {
|
|
1462
|
+
const { entry, node, repository } = input;
|
|
1463
|
+
let status = null;
|
|
1464
|
+
if (node) {
|
|
1465
|
+
const facts = {
|
|
1466
|
+
mergeStateStatus: node.mergeStateStatus ?? null,
|
|
1467
|
+
autoMergeRequest: toGitHubAutoMergeRequest(node.autoMergeRequest),
|
|
1468
|
+
viewerCanEnableAutoMerge: node.viewerCanEnableAutoMerge ?? false,
|
|
1469
|
+
viewerCanDisableAutoMerge: node.viewerCanDisableAutoMerge ?? false,
|
|
1470
|
+
viewerCanMergeAsAdmin: node.viewerCanMergeAsAdmin ?? false,
|
|
1471
|
+
viewerCanUpdateBranch: node.viewerCanUpdateBranch ?? false,
|
|
1472
|
+
repository: toGitHubRepositoryMergePolicy(repository),
|
|
1473
|
+
isMergeQueueEnabled: node.isMergeQueueEnabled ?? false,
|
|
1474
|
+
isInMergeQueue: node.isInMergeQueue ?? false,
|
|
1475
|
+
};
|
|
1476
|
+
const built = toCurrentPullRequestStatus(toBatchCurrentPullRequestItem(node, input.rollup ?? undefined), entry.target.headRef);
|
|
1477
|
+
status = built ? { ...built, forgeSpecific: { forge: "github", ...facts } } : null;
|
|
1478
|
+
}
|
|
1479
|
+
const cacheKey = buildCacheKey({
|
|
1480
|
+
cwd: entry.target.cwd,
|
|
1481
|
+
method: "getCurrentPullRequestStatus",
|
|
1482
|
+
args: {
|
|
1483
|
+
headRef: entry.target.headRef,
|
|
1484
|
+
headSha: entry.target.headSha,
|
|
1485
|
+
headRepositoryOwner: entry.target.headRepositoryOwner,
|
|
1486
|
+
},
|
|
1487
|
+
});
|
|
1488
|
+
// A direct or forced read can finish between this batch becoming due and
|
|
1489
|
+
// its checks query returning. Its cache write is newer than this cycle, so
|
|
1490
|
+
// converge on it instead of overwriting fresher data with the batch's.
|
|
1491
|
+
const existing = cache.get(cacheKey);
|
|
1492
|
+
if (existing && existing.expiresAt - ttlMs > input.startedAt) {
|
|
1493
|
+
updatePollTargetAfterSuccess({
|
|
1494
|
+
cwd: entry.target.cwd,
|
|
1495
|
+
headRef: entry.target.headRef,
|
|
1496
|
+
headSha: entry.target.headSha,
|
|
1497
|
+
headRepositoryOwner: entry.target.headRepositoryOwner,
|
|
1498
|
+
status: existing.value,
|
|
1499
|
+
notify: true,
|
|
1500
|
+
});
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
// Mirror the cached() write of getCurrentPullRequestStatus so direct reads
|
|
1504
|
+
// between polls reuse the batch result instead of spawning gh again.
|
|
1505
|
+
cache.set(cacheKey, { value: status, cwd: entry.target.cwd, expiresAt: deps.now() + ttlMs });
|
|
1506
|
+
updatePollTargetAfterSuccess({
|
|
1507
|
+
cwd: entry.target.cwd,
|
|
1508
|
+
headRef: entry.target.headRef,
|
|
1509
|
+
headSha: entry.target.headSha,
|
|
1510
|
+
headRepositoryOwner: entry.target.headRepositoryOwner,
|
|
1511
|
+
status,
|
|
1512
|
+
notify: true,
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
async function runGitHubPoll(target) {
|
|
1516
|
+
try {
|
|
1517
|
+
await api.getCurrentPullRequestStatus({
|
|
1518
|
+
cwd: target.cwd,
|
|
1519
|
+
headRef: target.headRef,
|
|
1520
|
+
headSha: target.headSha,
|
|
1521
|
+
headRepositoryOwner: target.headRepositoryOwner,
|
|
1522
|
+
reason: "self-heal-github",
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
catch (error) {
|
|
1526
|
+
updatePollTargetAfterError(target, error);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function updatePollTargetAfterError(target, error) {
|
|
1530
|
+
if (target.retainCount <= 0) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
target.consecutiveErrors += 1;
|
|
1534
|
+
for (const callback of target.errorCallbacks) {
|
|
1535
|
+
callback(error);
|
|
1536
|
+
}
|
|
1537
|
+
scheduleGitHubPoll(target);
|
|
1538
|
+
}
|
|
1539
|
+
function closeGitHubPollTarget(target) {
|
|
1540
|
+
target.nextDueAt = null;
|
|
1541
|
+
target.pollCycleStartedAt = null;
|
|
1542
|
+
target.retainCount = 0;
|
|
1543
|
+
target.callbacks.clear();
|
|
1544
|
+
target.errorCallbacks.clear();
|
|
1545
|
+
}
|
|
1546
|
+
api = {
|
|
1547
|
+
authProbeCanThrow: true,
|
|
1548
|
+
listPullRequests(input) {
|
|
1549
|
+
return cached({
|
|
1550
|
+
cwd: input.cwd,
|
|
1551
|
+
method: "listPullRequests",
|
|
1552
|
+
args: { query: input.query ?? "", limit: input.limit ?? 20 },
|
|
1553
|
+
readOptions: input,
|
|
1554
|
+
load: async () => {
|
|
1555
|
+
const items = await runGhJson([
|
|
1556
|
+
"pr",
|
|
1557
|
+
"list",
|
|
1558
|
+
"--search",
|
|
1559
|
+
input.query ?? "",
|
|
1560
|
+
"--json",
|
|
1561
|
+
"number,title,url,state,body,labels,baseRefName,headRefName,updatedAt",
|
|
1562
|
+
"--limit",
|
|
1563
|
+
String(input.limit ?? 20),
|
|
1564
|
+
], { cwd: input.cwd }, z.array(GitHubPullRequestSummarySchema), "[]");
|
|
1565
|
+
return items.map(toPullRequestSummary);
|
|
1566
|
+
},
|
|
1567
|
+
});
|
|
1568
|
+
},
|
|
1569
|
+
listIssues(input) {
|
|
1570
|
+
return cached({
|
|
1571
|
+
cwd: input.cwd,
|
|
1572
|
+
method: "listIssues",
|
|
1573
|
+
args: { query: input.query ?? "", limit: input.limit ?? 20 },
|
|
1574
|
+
readOptions: input,
|
|
1575
|
+
load: async () => {
|
|
1576
|
+
const items = await runGhJson([
|
|
1577
|
+
"issue",
|
|
1578
|
+
"list",
|
|
1579
|
+
"--search",
|
|
1580
|
+
input.query ?? "",
|
|
1581
|
+
"--json",
|
|
1582
|
+
"number,title,url,state,body,labels,updatedAt",
|
|
1583
|
+
"--limit",
|
|
1584
|
+
String(input.limit ?? 20),
|
|
1585
|
+
], { cwd: input.cwd }, z.array(GitHubIssueSummarySchema), "[]");
|
|
1586
|
+
return items.map(toIssueSummary);
|
|
1587
|
+
},
|
|
1588
|
+
});
|
|
1589
|
+
},
|
|
1590
|
+
getPullRequest(input) {
|
|
1591
|
+
return cached({
|
|
1592
|
+
cwd: input.cwd,
|
|
1593
|
+
method: "getPullRequest",
|
|
1594
|
+
args: { number: input.number },
|
|
1595
|
+
readOptions: input,
|
|
1596
|
+
load: async () => {
|
|
1597
|
+
const item = await runGhJson([
|
|
1598
|
+
"pr",
|
|
1599
|
+
"view",
|
|
1600
|
+
String(input.number),
|
|
1601
|
+
"--json",
|
|
1602
|
+
"number,title,url,state,body,labels,baseRefName,headRefName,updatedAt",
|
|
1603
|
+
], { cwd: input.cwd }, GitHubPullRequestSummarySchema, "{}");
|
|
1604
|
+
return toPullRequestSummary(item);
|
|
1605
|
+
},
|
|
1606
|
+
});
|
|
1607
|
+
},
|
|
1608
|
+
async getPullRequestHeadRef(input) {
|
|
1609
|
+
const pullRequest = await this.getPullRequest(input);
|
|
1610
|
+
return pullRequest.headRefName;
|
|
1611
|
+
},
|
|
1612
|
+
defaultCheckoutRefs({ changeRequestNumber }) {
|
|
1613
|
+
return [
|
|
1614
|
+
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
|
1615
|
+
{ remoteName: "upstream", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
|
1616
|
+
];
|
|
1617
|
+
},
|
|
1618
|
+
buildPrLocalBranchName({ headRef, checkoutTarget }) {
|
|
1619
|
+
const owner = checkoutTarget.isCrossRepository
|
|
1620
|
+
? normalizeGitHubOwnerForBranch(checkoutTarget.headOwnerLogin)
|
|
1621
|
+
: null;
|
|
1622
|
+
return owner ? `${owner}/${headRef}` : headRef;
|
|
1623
|
+
},
|
|
1624
|
+
supportsCrossRepoCheckoutWithoutRefs: true,
|
|
1625
|
+
getPullRequestCheckoutTarget(input) {
|
|
1626
|
+
return cached({
|
|
1627
|
+
cwd: input.cwd,
|
|
1628
|
+
method: "getPullRequestCheckoutTarget",
|
|
1629
|
+
args: { number: input.number },
|
|
1630
|
+
readOptions: input,
|
|
1631
|
+
load: async () => {
|
|
1632
|
+
const repo = await getGitHubRepoView({ cwd: input.cwd, run });
|
|
1633
|
+
const owner = repo?.owner?.login;
|
|
1634
|
+
const name = repo?.name;
|
|
1635
|
+
if (!owner || !name) {
|
|
1636
|
+
throw new Error("Unable to resolve GitHub repository for pull request checkout");
|
|
1637
|
+
}
|
|
1638
|
+
const parsed = await runGhJson([
|
|
1639
|
+
"api",
|
|
1640
|
+
"graphql",
|
|
1641
|
+
"-f",
|
|
1642
|
+
`query=${PULL_REQUEST_CHECKOUT_TARGET_QUERY}`,
|
|
1643
|
+
"-F",
|
|
1644
|
+
`owner=${owner}`,
|
|
1645
|
+
"-F",
|
|
1646
|
+
`name=${name}`,
|
|
1647
|
+
"-F",
|
|
1648
|
+
`number=${input.number}`,
|
|
1649
|
+
], { cwd: input.cwd }, PullRequestCheckoutTargetSchema, "{}");
|
|
1650
|
+
return toPullRequestCheckoutTarget(parsed);
|
|
1651
|
+
},
|
|
1652
|
+
});
|
|
1653
|
+
},
|
|
1654
|
+
getCurrentPullRequestStatus(input) {
|
|
1655
|
+
return cached({
|
|
1656
|
+
cwd: input.cwd,
|
|
1657
|
+
method: "getCurrentPullRequestStatus",
|
|
1658
|
+
args: {
|
|
1659
|
+
headRef: input.headRef,
|
|
1660
|
+
headSha: input.headSha,
|
|
1661
|
+
headRepositoryOwner: input.headRepositoryOwner,
|
|
1662
|
+
},
|
|
1663
|
+
readOptions: input,
|
|
1664
|
+
load: async () => {
|
|
1665
|
+
const status = await resolveCurrentPullRequestView({
|
|
1666
|
+
cwd: input.cwd,
|
|
1667
|
+
headRef: input.headRef,
|
|
1668
|
+
headSha: input.headSha,
|
|
1669
|
+
headRepositoryOwner: input.headRepositoryOwner,
|
|
1670
|
+
run,
|
|
1671
|
+
});
|
|
1672
|
+
return addCurrentPullRequestGithubFacts({ cwd: input.cwd, status, run });
|
|
1673
|
+
},
|
|
1674
|
+
}).then((status) => {
|
|
1675
|
+
updatePollTargetAfterSuccess({
|
|
1676
|
+
cwd: input.cwd,
|
|
1677
|
+
headRef: input.headRef,
|
|
1678
|
+
headSha: input.headSha,
|
|
1679
|
+
headRepositoryOwner: input.headRepositoryOwner,
|
|
1680
|
+
status,
|
|
1681
|
+
notify: input.reason === "self-heal-github",
|
|
1682
|
+
});
|
|
1683
|
+
return status;
|
|
1684
|
+
});
|
|
1685
|
+
},
|
|
1686
|
+
getPullRequestTimeline(input) {
|
|
1687
|
+
return cached({
|
|
1688
|
+
cwd: input.cwd,
|
|
1689
|
+
method: "getPullRequestTimeline",
|
|
1690
|
+
args: { prNumber: input.prNumber },
|
|
1691
|
+
readOptions: input,
|
|
1692
|
+
load: async () => {
|
|
1693
|
+
try {
|
|
1694
|
+
const parsed = await runGhJson([
|
|
1695
|
+
"api",
|
|
1696
|
+
"graphql",
|
|
1697
|
+
"-f",
|
|
1698
|
+
`query=${PULL_REQUEST_TIMELINE_QUERY}`,
|
|
1699
|
+
"-F",
|
|
1700
|
+
`owner=${input.repoOwner}`,
|
|
1701
|
+
"-F",
|
|
1702
|
+
`name=${input.repoName}`,
|
|
1703
|
+
"-F",
|
|
1704
|
+
`number=${input.prNumber}`,
|
|
1705
|
+
], { cwd: input.cwd }, PullRequestTimelineGraphqlSchema, "{}");
|
|
1706
|
+
return toPullRequestTimeline(parsed, {
|
|
1707
|
+
prNumber: input.prNumber,
|
|
1708
|
+
repoOwner: input.repoOwner,
|
|
1709
|
+
repoName: input.repoName,
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
catch (error) {
|
|
1713
|
+
return {
|
|
1714
|
+
prNumber: input.prNumber,
|
|
1715
|
+
repoOwner: input.repoOwner,
|
|
1716
|
+
repoName: input.repoName,
|
|
1717
|
+
items: [],
|
|
1718
|
+
truncated: false,
|
|
1719
|
+
error: mapPullRequestTimelineError(error),
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1723
|
+
});
|
|
1724
|
+
},
|
|
1725
|
+
getCheckDetails(input) {
|
|
1726
|
+
const { repoOwner, repoName, checkRunId } = input;
|
|
1727
|
+
if (!repoOwner || !repoName) {
|
|
1728
|
+
throw new Error("GitHub getCheckDetails requires repoOwner and repoName");
|
|
1729
|
+
}
|
|
1730
|
+
if (checkRunId === undefined) {
|
|
1731
|
+
throw new Error("GitHub getCheckDetails requires checkRunId");
|
|
1732
|
+
}
|
|
1733
|
+
return cached({
|
|
1734
|
+
cwd: input.cwd,
|
|
1735
|
+
method: "getCheckDetails",
|
|
1736
|
+
args: {
|
|
1737
|
+
repoOwner,
|
|
1738
|
+
repoName,
|
|
1739
|
+
checkRunId,
|
|
1740
|
+
workflowRunId: input.workflowRunId,
|
|
1741
|
+
},
|
|
1742
|
+
readOptions: input,
|
|
1743
|
+
load: async () => {
|
|
1744
|
+
const repoPath = `repos/${repoOwner}/${repoName}`;
|
|
1745
|
+
const checkRun = toGitHubCheckRunDetails(await runGhJson(["api", `${repoPath}/check-runs/${checkRunId}`], { cwd: input.cwd }, GitHubCheckRunDetailsSchema, "{}"));
|
|
1746
|
+
const annotations = toGitHubCheckAnnotations(await runGhJson([
|
|
1747
|
+
"api",
|
|
1748
|
+
`${repoPath}/check-runs/${checkRunId}/annotations`,
|
|
1749
|
+
"-f",
|
|
1750
|
+
`per_page=${CHECK_ANNOTATION_PAGE_MAX}`,
|
|
1751
|
+
], {
|
|
1752
|
+
cwd: input.cwd,
|
|
1753
|
+
}, GitHubCheckAnnotationsSchema, "[]"));
|
|
1754
|
+
const workflowRunId = input.workflowRunId ?? checkRun.workflowRunId ?? null;
|
|
1755
|
+
const failedJobs = [];
|
|
1756
|
+
let truncated = annotations.length >= CHECK_ANNOTATION_PAGE_MAX;
|
|
1757
|
+
if (typeof workflowRunId === "number") {
|
|
1758
|
+
const jobs = toGitHubActionsJobs(await runGhJson([
|
|
1759
|
+
"api",
|
|
1760
|
+
`${repoPath}/actions/runs/${workflowRunId}/jobs`,
|
|
1761
|
+
"-f",
|
|
1762
|
+
`per_page=${ACTIONS_JOB_PAGE_MAX}`,
|
|
1763
|
+
], {
|
|
1764
|
+
cwd: input.cwd,
|
|
1765
|
+
}, GitHubActionsJobsSchema, "{}"));
|
|
1766
|
+
const failed = jobs.filter(isFailedActionsJob);
|
|
1767
|
+
truncated || (truncated = jobs.length >= ACTIONS_JOB_PAGE_MAX);
|
|
1768
|
+
truncated || (truncated = failed.length > FAILED_CHECK_JOB_LIMIT);
|
|
1769
|
+
for (const job of failed.slice(0, FAILED_CHECK_JOB_LIMIT)) {
|
|
1770
|
+
const log = await getCachedCheckLogTail({
|
|
1771
|
+
cwd: input.cwd,
|
|
1772
|
+
repoPath,
|
|
1773
|
+
job,
|
|
1774
|
+
run,
|
|
1775
|
+
cache: checkLogTailCache,
|
|
1776
|
+
});
|
|
1777
|
+
truncated || (truncated = log.logTruncated);
|
|
1778
|
+
failedJobs.push({
|
|
1779
|
+
jobId: job.jobId,
|
|
1780
|
+
name: job.name,
|
|
1781
|
+
status: job.status,
|
|
1782
|
+
conclusion: job.conclusion,
|
|
1783
|
+
url: job.url,
|
|
1784
|
+
logTail: log.logTail,
|
|
1785
|
+
logTruncated: log.logTruncated,
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
checkRunId: checkRun.checkRunId,
|
|
1791
|
+
workflowRunId,
|
|
1792
|
+
name: checkRun.name,
|
|
1793
|
+
status: checkRun.status,
|
|
1794
|
+
conclusion: checkRun.conclusion,
|
|
1795
|
+
url: checkRun.url,
|
|
1796
|
+
detailsUrl: checkRun.detailsUrl,
|
|
1797
|
+
output: checkRun.output,
|
|
1798
|
+
annotations,
|
|
1799
|
+
failedJobs,
|
|
1800
|
+
truncated,
|
|
1801
|
+
};
|
|
1802
|
+
},
|
|
1803
|
+
});
|
|
1804
|
+
},
|
|
1805
|
+
async searchRepositories(input) {
|
|
1806
|
+
const limit = input.limit ?? 20;
|
|
1807
|
+
const query = input.query.trim();
|
|
1808
|
+
if (query.length === 0) {
|
|
1809
|
+
const [stdout, cloneProtocol] = await Promise.all([
|
|
1810
|
+
run([
|
|
1811
|
+
"repo",
|
|
1812
|
+
"list",
|
|
1813
|
+
"--json",
|
|
1814
|
+
"id,name,nameWithOwner,description,isPrivate,updatedAt,sshUrl,url",
|
|
1815
|
+
"--limit",
|
|
1816
|
+
String(limit),
|
|
1817
|
+
], { cwd: input.cwd }),
|
|
1818
|
+
resolveConfiguredCloneProtocol(input.cwd, run),
|
|
1819
|
+
]);
|
|
1820
|
+
return parseRepositoryList(stdout, cloneProtocol);
|
|
1821
|
+
}
|
|
1822
|
+
const [stdout, cloneProtocol] = await Promise.all([
|
|
1823
|
+
run([
|
|
1824
|
+
"search",
|
|
1825
|
+
"repos",
|
|
1826
|
+
query,
|
|
1827
|
+
"--json",
|
|
1828
|
+
"id,name,fullName,description,isPrivate,updatedAt,url",
|
|
1829
|
+
"--sort",
|
|
1830
|
+
"updated",
|
|
1831
|
+
"--order",
|
|
1832
|
+
"desc",
|
|
1833
|
+
"--limit",
|
|
1834
|
+
String(limit),
|
|
1835
|
+
], { cwd: input.cwd }),
|
|
1836
|
+
resolveConfiguredCloneProtocol(input.cwd, run),
|
|
1837
|
+
]);
|
|
1838
|
+
return parseRepositorySearch(stdout, cloneProtocol);
|
|
1839
|
+
},
|
|
1840
|
+
async searchIssuesAndPrs(input) {
|
|
1841
|
+
if (input.force && !input.reason) {
|
|
1842
|
+
throw new Error("ForgeService forced read requires a reason");
|
|
1843
|
+
}
|
|
1844
|
+
const kinds = normalizeForgeSearchKinds(input.kinds);
|
|
1845
|
+
const shouldFetchIssues = kinds.includes("issue");
|
|
1846
|
+
const shouldFetchPullRequests = kinds.includes("change_request");
|
|
1847
|
+
const readOptions = input.force
|
|
1848
|
+
? { force: true, reason: input.reason }
|
|
1849
|
+
: { force: false, reason: input.reason };
|
|
1850
|
+
const enterpriseHost = await resolveRepoHostCached(input.cwd).catch(() => null);
|
|
1851
|
+
const query = normalizeGitHubSearchQuery(input.query, enterpriseHost);
|
|
1852
|
+
const [issuesResult, prsResult] = await Promise.allSettled([
|
|
1853
|
+
shouldFetchIssues
|
|
1854
|
+
? this.listIssues({
|
|
1855
|
+
cwd: input.cwd,
|
|
1856
|
+
query,
|
|
1857
|
+
limit: input.limit,
|
|
1858
|
+
...readOptions,
|
|
1859
|
+
})
|
|
1860
|
+
: Promise.resolve(null),
|
|
1861
|
+
shouldFetchPullRequests
|
|
1862
|
+
? this.listPullRequests({
|
|
1863
|
+
cwd: input.cwd,
|
|
1864
|
+
query,
|
|
1865
|
+
limit: input.limit,
|
|
1866
|
+
...readOptions,
|
|
1867
|
+
})
|
|
1868
|
+
: Promise.resolve(null),
|
|
1869
|
+
]);
|
|
1870
|
+
const items = [];
|
|
1871
|
+
const requestedResults = [
|
|
1872
|
+
shouldFetchIssues ? issuesResult : null,
|
|
1873
|
+
shouldFetchPullRequests ? prsResult : null,
|
|
1874
|
+
].filter((result) => result !== null);
|
|
1875
|
+
if (requestedResults.length > 0 &&
|
|
1876
|
+
requestedResults.every((result) => result.status === "rejected" &&
|
|
1877
|
+
(result.reason instanceof GitHubCliMissingError ||
|
|
1878
|
+
result.reason instanceof GitHubAuthenticationError))) {
|
|
1879
|
+
const hasMissingCli = requestedResults.some((result) => result.status === "rejected" && result.reason instanceof GitHubCliMissingError);
|
|
1880
|
+
return createUnavailableSearchResult(hasMissingCli ? "cli_missing" : "unauthenticated");
|
|
1881
|
+
}
|
|
1882
|
+
if (shouldFetchIssues && issuesResult.status === "fulfilled") {
|
|
1883
|
+
for (const item of issuesResult.value ?? []) {
|
|
1884
|
+
items.push({
|
|
1885
|
+
kind: "issue",
|
|
1886
|
+
number: item.number,
|
|
1887
|
+
title: item.title,
|
|
1888
|
+
url: item.url,
|
|
1889
|
+
state: item.state,
|
|
1890
|
+
body: item.body,
|
|
1891
|
+
labels: item.labels,
|
|
1892
|
+
baseRefName: null,
|
|
1893
|
+
headRefName: null,
|
|
1894
|
+
updatedAt: item.updatedAt,
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (shouldFetchPullRequests && prsResult.status === "fulfilled") {
|
|
1899
|
+
for (const item of prsResult.value ?? []) {
|
|
1900
|
+
items.push({
|
|
1901
|
+
kind: "change_request",
|
|
1902
|
+
number: item.number,
|
|
1903
|
+
title: item.title,
|
|
1904
|
+
url: item.url,
|
|
1905
|
+
state: item.state,
|
|
1906
|
+
body: item.body,
|
|
1907
|
+
labels: item.labels,
|
|
1908
|
+
baseRefName: item.baseRefName,
|
|
1909
|
+
headRefName: item.headRefName,
|
|
1910
|
+
updatedAt: item.updatedAt,
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
items.sort((left, right) => {
|
|
1915
|
+
const leftTime = parseOptionalTime(left.updatedAt ?? null);
|
|
1916
|
+
const rightTime = parseOptionalTime(right.updatedAt ?? null);
|
|
1917
|
+
return rightTime - leftTime;
|
|
1918
|
+
});
|
|
1919
|
+
return {
|
|
1920
|
+
items,
|
|
1921
|
+
featuresEnabled: true,
|
|
1922
|
+
authState: "authenticated",
|
|
1923
|
+
githubFeaturesEnabled: true,
|
|
1924
|
+
};
|
|
1925
|
+
},
|
|
1926
|
+
async createPullRequest(input) {
|
|
1927
|
+
// Resolve the owner/name slug offline from the origin remote first so a
|
|
1928
|
+
// transient `gh repo view` failure can't block PR creation on github.com.
|
|
1929
|
+
// `gh repo view` is the fallback: it auto-routes to the cwd remote's host
|
|
1930
|
+
// (covering GitHub Enterprise Server) and survives a renamed repo.
|
|
1931
|
+
let slug = await resolveGitHubSlugFromOrigin(input.cwd);
|
|
1932
|
+
if (!slug) {
|
|
1933
|
+
const repoView = await getGitHubRepoView({ cwd: input.cwd, run });
|
|
1934
|
+
slug =
|
|
1935
|
+
repoView?.owner?.login && repoView.name
|
|
1936
|
+
? `${repoView.owner.login}/${repoView.name}`
|
|
1937
|
+
: null;
|
|
1938
|
+
}
|
|
1939
|
+
if (!slug) {
|
|
1940
|
+
throw new Error("Unable to resolve GitHub repository for pull request creation");
|
|
1941
|
+
}
|
|
1942
|
+
const args = ["api", "-X", "POST", `repos/${slug}/pulls`, "-f", `title=${input.title}`];
|
|
1943
|
+
args.push("-f", `head=${input.head}`);
|
|
1944
|
+
args.push("-f", `base=${input.base}`);
|
|
1945
|
+
if (input.body) {
|
|
1946
|
+
args.push("-f", `body=${input.body}`);
|
|
1947
|
+
}
|
|
1948
|
+
const parsed = await runGhJson(args, { cwd: input.cwd }, z.object({
|
|
1949
|
+
url: z.string(),
|
|
1950
|
+
number: z.number(),
|
|
1951
|
+
}), "{}");
|
|
1952
|
+
return parsed;
|
|
1953
|
+
},
|
|
1954
|
+
async mergePullRequest(input) {
|
|
1955
|
+
assertDirectPullRequestMergeReady(input);
|
|
1956
|
+
await run(["pr", "merge", String(input.prNumber), `--${input.mergeMethod}`], {
|
|
1957
|
+
cwd: input.cwd,
|
|
1958
|
+
envOverlay: { GH_PROMPT_DISABLED: "1" },
|
|
1959
|
+
});
|
|
1960
|
+
return { success: true };
|
|
1961
|
+
},
|
|
1962
|
+
async enablePullRequestAutoMerge(input) {
|
|
1963
|
+
assertPullRequestAutoMergeEnableReady(input);
|
|
1964
|
+
await run(["pr", "merge", String(input.prNumber), "--auto", `--${input.mergeMethod}`], {
|
|
1965
|
+
cwd: input.cwd,
|
|
1966
|
+
envOverlay: { GH_PROMPT_DISABLED: "1" },
|
|
1967
|
+
});
|
|
1968
|
+
return { success: true };
|
|
1969
|
+
},
|
|
1970
|
+
async disablePullRequestAutoMerge(input) {
|
|
1971
|
+
assertPullRequestAutoMergeDisableReady(input);
|
|
1972
|
+
await run(["pr", "merge", String(input.prNumber), "--disable-auto"], {
|
|
1973
|
+
cwd: input.cwd,
|
|
1974
|
+
envOverlay: { GH_PROMPT_DISABLED: "1" },
|
|
1975
|
+
});
|
|
1976
|
+
return { success: true };
|
|
1977
|
+
},
|
|
1978
|
+
isAuthenticated(input) {
|
|
1979
|
+
return cached({
|
|
1980
|
+
cwd: input.cwd,
|
|
1981
|
+
method: "isAuthenticated",
|
|
1982
|
+
args: {},
|
|
1983
|
+
readOptions: input,
|
|
1984
|
+
load: async () => {
|
|
1985
|
+
try {
|
|
1986
|
+
await run(["auth", "status"], { cwd: input.cwd });
|
|
1987
|
+
return true;
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
if (isGitHubAuthenticationError(error)) {
|
|
1991
|
+
throw error;
|
|
1992
|
+
}
|
|
1993
|
+
if (error instanceof GitHubCommandError && isAuthFailureText(error.stderr)) {
|
|
1994
|
+
throw new GitHubAuthenticationError({ stderr: error.stderr });
|
|
1995
|
+
}
|
|
1996
|
+
throw error;
|
|
1997
|
+
}
|
|
1998
|
+
},
|
|
1999
|
+
});
|
|
2000
|
+
},
|
|
2001
|
+
retainCurrentPullRequestStatusPoll(input) {
|
|
2002
|
+
const key = getPollTargetKey(input);
|
|
2003
|
+
let target = pollTargets.get(key);
|
|
2004
|
+
if (!target) {
|
|
2005
|
+
target = {
|
|
2006
|
+
cwd: input.cwd,
|
|
2007
|
+
headRef: input.headRef,
|
|
2008
|
+
headSha: input.headSha,
|
|
2009
|
+
headRepositoryOwner: input.headRepositoryOwner,
|
|
2010
|
+
retainCount: 0,
|
|
2011
|
+
nextDueAt: null,
|
|
2012
|
+
pollCycleStartedAt: null,
|
|
2013
|
+
latestStatus: null,
|
|
2014
|
+
consecutiveErrors: 0,
|
|
2015
|
+
callbacks: new Set(),
|
|
2016
|
+
errorCallbacks: new Set(),
|
|
2017
|
+
};
|
|
2018
|
+
pollTargets.set(key, target);
|
|
2019
|
+
// Warm the host and slug caches so the first poll flush can already
|
|
2020
|
+
// batch this target instead of taking the legacy per-target path.
|
|
2021
|
+
void resolveRepoHostCached(input.cwd).catch(() => { });
|
|
2022
|
+
void resolveRepoSlugCached(input.cwd);
|
|
2023
|
+
}
|
|
2024
|
+
const isNewlyRetained = target.retainCount === 0;
|
|
2025
|
+
target.retainCount += 1;
|
|
2026
|
+
if (input.onStatus) {
|
|
2027
|
+
target.callbacks.add(input.onStatus);
|
|
2028
|
+
}
|
|
2029
|
+
if (input.onError) {
|
|
2030
|
+
target.errorCallbacks.add(input.onError);
|
|
2031
|
+
}
|
|
2032
|
+
if (isNewlyRetained) {
|
|
2033
|
+
scheduleImmediateGitHubPoll(target);
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
scheduleGitHubPoll(target);
|
|
2037
|
+
}
|
|
2038
|
+
let unsubscribed = false;
|
|
2039
|
+
return {
|
|
2040
|
+
unsubscribe: () => {
|
|
2041
|
+
if (unsubscribed) {
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
unsubscribed = true;
|
|
2045
|
+
if (input.onStatus) {
|
|
2046
|
+
target.callbacks.delete(input.onStatus);
|
|
2047
|
+
}
|
|
2048
|
+
if (input.onError) {
|
|
2049
|
+
target.errorCallbacks.delete(input.onError);
|
|
2050
|
+
}
|
|
2051
|
+
target.retainCount -= 1;
|
|
2052
|
+
if (target.retainCount > 0) {
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
closeGitHubPollTarget(target);
|
|
2056
|
+
pollTargets.delete(key);
|
|
2057
|
+
armGitHubPollTimer();
|
|
2058
|
+
},
|
|
2059
|
+
};
|
|
2060
|
+
},
|
|
2061
|
+
invalidate(input) {
|
|
2062
|
+
// Local checkout mutations that can alter the current PR identity or PR status
|
|
2063
|
+
// must call this with the affected cwd before broadcasting fresh git state.
|
|
2064
|
+
for (const [key, entry] of cache.entries()) {
|
|
2065
|
+
if (entry.cwd === input.cwd) {
|
|
2066
|
+
cache.delete(key);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
for (const [key, entry] of inFlight.entries()) {
|
|
2070
|
+
if (entry.cwd === input.cwd) {
|
|
2071
|
+
inFlight.delete(key);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
// Drop the cached host and slug so a changed remote re-resolves GH_HOST
|
|
2075
|
+
// and the batch-poll repository identity instead of reusing stale ones.
|
|
2076
|
+
repoHostByCwd.delete(input.cwd);
|
|
2077
|
+
repoSlugByCwd.delete(input.cwd);
|
|
2078
|
+
},
|
|
2079
|
+
dispose() {
|
|
2080
|
+
if (githubPollTimer) {
|
|
2081
|
+
clearTimeout(githubPollTimer);
|
|
2082
|
+
githubPollTimer = null;
|
|
2083
|
+
}
|
|
2084
|
+
for (const target of pollTargets.values()) {
|
|
2085
|
+
closeGitHubPollTarget(target);
|
|
2086
|
+
}
|
|
2087
|
+
pollTargets.clear();
|
|
2088
|
+
pollRepositoryRedirects.clear();
|
|
2089
|
+
pollPausedUntilByHost.clear();
|
|
2090
|
+
rateLimitResetLoads.clear();
|
|
2091
|
+
},
|
|
2092
|
+
};
|
|
2093
|
+
return api;
|
|
2094
|
+
}
|
|
2095
|
+
function normalizeGitHubOwnerForBranch(owner) {
|
|
2096
|
+
const normalized = owner?.trim().toLowerCase() ?? "";
|
|
2097
|
+
return /^[a-z0-9-]+$/.test(normalized) ? normalized : null;
|
|
2098
|
+
}
|
|
2099
|
+
function getGithubStatusFacts(status) {
|
|
2100
|
+
const forgeSpecific = status?.forgeSpecific;
|
|
2101
|
+
return isGitHubPullRequestStatusFacts(forgeSpecific) ? forgeSpecific : null;
|
|
2102
|
+
}
|
|
2103
|
+
function assertDirectPullRequestMergeReady(input) {
|
|
2104
|
+
const github = getGithubStatusFacts(input.status);
|
|
2105
|
+
if (!github) {
|
|
2106
|
+
throw new Error("GitHub merge facts are unavailable for this pull request");
|
|
2107
|
+
}
|
|
2108
|
+
if (!DIRECT_PULL_REQUEST_MERGE_STATE_ALLOWLIST.has(github.mergeStateStatus ?? "")) {
|
|
2109
|
+
throw new Error("GitHub does not report this pull request as ready for direct merge");
|
|
2110
|
+
}
|
|
2111
|
+
if (github.isMergeQueueEnabled || github.isInMergeQueue) {
|
|
2112
|
+
throw new Error("Direct merge is not available because this repository uses a merge queue");
|
|
2113
|
+
}
|
|
2114
|
+
if (github.autoMergeRequest !== null) {
|
|
2115
|
+
throw new Error("Direct merge is not available because auto-merge is already enabled");
|
|
2116
|
+
}
|
|
2117
|
+
if (!isPullRequestMergeMethodAllowed(github.repository, input.mergeMethod)) {
|
|
2118
|
+
throw new Error(`Direct merge is not available because ${input.mergeMethod} is disabled`);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
export function assertPullRequestAutoMergeEnableReady(input) {
|
|
2122
|
+
const github = getGithubStatusFacts(input.status);
|
|
2123
|
+
if (!github) {
|
|
2124
|
+
throw new Error("GitHub auto-merge facts are unavailable for this pull request");
|
|
2125
|
+
}
|
|
2126
|
+
if (github.mergeStateStatus !== "BLOCKED") {
|
|
2127
|
+
throw new Error("GitHub does not report this pull request as blocked for auto-merge");
|
|
2128
|
+
}
|
|
2129
|
+
if (!github.viewerCanEnableAutoMerge) {
|
|
2130
|
+
throw new Error("GitHub does not allow this viewer to enable auto-merge");
|
|
2131
|
+
}
|
|
2132
|
+
if (!github.repository.autoMergeAllowed) {
|
|
2133
|
+
throw new Error("Auto-merge is disabled for this repository");
|
|
2134
|
+
}
|
|
2135
|
+
if (!isPullRequestMergeMethodAllowed(github.repository, input.mergeMethod)) {
|
|
2136
|
+
throw new Error(`Auto-merge is not available because ${input.mergeMethod} is disabled`);
|
|
2137
|
+
}
|
|
2138
|
+
if (github.autoMergeRequest !== null) {
|
|
2139
|
+
throw new Error("Auto-merge is already enabled for this pull request");
|
|
2140
|
+
}
|
|
2141
|
+
if (github.isMergeQueueEnabled || github.isInMergeQueue) {
|
|
2142
|
+
throw new Error("Auto-merge is not available because this repository uses a merge queue");
|
|
2143
|
+
}
|
|
2144
|
+
if (input.status?.mergeable === "CONFLICTING") {
|
|
2145
|
+
throw new Error("Auto-merge is not available because this pull request has conflicts");
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
export function assertPullRequestAutoMergeDisableReady(input) {
|
|
2149
|
+
const github = getGithubStatusFacts(input.status);
|
|
2150
|
+
if (!github) {
|
|
2151
|
+
throw new Error("GitHub auto-merge facts are unavailable for this pull request");
|
|
2152
|
+
}
|
|
2153
|
+
if (github.autoMergeRequest === null) {
|
|
2154
|
+
throw new Error("Auto-merge is not enabled for this pull request");
|
|
2155
|
+
}
|
|
2156
|
+
if (!github.viewerCanDisableAutoMerge) {
|
|
2157
|
+
throw new Error("GitHub does not allow this viewer to disable auto-merge");
|
|
2158
|
+
}
|
|
2159
|
+
if (github.isMergeQueueEnabled || github.isInMergeQueue) {
|
|
2160
|
+
throw new Error("Auto-merge is not available because this repository uses a merge queue");
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
export function isPullRequestMergeMethodAllowed(repository, method) {
|
|
2164
|
+
if (method === "squash") {
|
|
2165
|
+
return repository.squashMergeAllowed;
|
|
2166
|
+
}
|
|
2167
|
+
if (method === "merge") {
|
|
2168
|
+
return repository.mergeCommitAllowed;
|
|
2169
|
+
}
|
|
2170
|
+
return repository.rebaseMergeAllowed;
|
|
2171
|
+
}
|
|
2172
|
+
export function computeGithubNextInterval(status, consecutiveErrors) {
|
|
2173
|
+
const baseInterval = isGitHubStatusPending(status)
|
|
2174
|
+
? GITHUB_POLL_FAST_INTERVAL_MS
|
|
2175
|
+
: GITHUB_POLL_SLOW_INTERVAL_MS;
|
|
2176
|
+
if (consecutiveErrors <= 1) {
|
|
2177
|
+
return baseInterval;
|
|
2178
|
+
}
|
|
2179
|
+
return Math.min(baseInterval * 2 ** (consecutiveErrors - 1), GITHUB_POLL_ERROR_BACKOFF_CAP_MS);
|
|
2180
|
+
}
|
|
2181
|
+
function isGitHubStatusPending(status) {
|
|
2182
|
+
if (!status) {
|
|
2183
|
+
return false;
|
|
2184
|
+
}
|
|
2185
|
+
if (status.checksStatus === "pending") {
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
return status.checks.some((check) => check.status === "pending");
|
|
2189
|
+
}
|
|
2190
|
+
async function resolveGhPath() {
|
|
2191
|
+
return findExecutable("gh");
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Detect whether a self-hosted host is GitHub Enterprise Server by asking the
|
|
2195
|
+
* local gh CLI whether it is already authenticated to that exact hostname.
|
|
2196
|
+
* Cloud github.com short-circuits via the registry's host match before this runs.
|
|
2197
|
+
*/
|
|
2198
|
+
export async function probeGitHubHost(host) {
|
|
2199
|
+
return probeGitHubAuthStatus(host);
|
|
2200
|
+
}
|
|
2201
|
+
async function probeGitHubAuthStatus(host) {
|
|
2202
|
+
return probeHostViaCliAuthStatus({
|
|
2203
|
+
cli: "gh",
|
|
2204
|
+
host,
|
|
2205
|
+
envOverlay: { ...GITHUB_ENV, GH_PROMPT_DISABLED: "1" },
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
async function probeGitHubAuthStatusForRouting(host) {
|
|
2209
|
+
const ghPath = await findExecutable("gh");
|
|
2210
|
+
if (!ghPath) {
|
|
2211
|
+
throw new GitHubCliMissingError();
|
|
2212
|
+
}
|
|
2213
|
+
const args = ["auth", "status", "--hostname", host];
|
|
2214
|
+
try {
|
|
2215
|
+
await execCommand(ghPath, args, {
|
|
2216
|
+
envOverlay: { ...GITHUB_ENV, GH_PROMPT_DISABLED: "1" },
|
|
2217
|
+
timeout: CLI_AUTH_PROBE_TIMEOUT_MS,
|
|
2218
|
+
});
|
|
2219
|
+
return true;
|
|
2220
|
+
}
|
|
2221
|
+
catch (error) {
|
|
2222
|
+
const normalized = githubCliRunner.normalizeError(error, {
|
|
2223
|
+
args,
|
|
2224
|
+
cwd: process.cwd(),
|
|
2225
|
+
timeoutMs: CLI_AUTH_PROBE_TIMEOUT_MS,
|
|
2226
|
+
});
|
|
2227
|
+
if (isGitHubAuthenticationError(normalized)) {
|
|
2228
|
+
return false;
|
|
2229
|
+
}
|
|
2230
|
+
throw new GitHubEnterpriseHostProbeError({ host, cause: normalized });
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
const githubCliRunner = createForgeCliRunner({
|
|
2234
|
+
binary: "gh",
|
|
2235
|
+
envOverlay: GITHUB_ENV,
|
|
2236
|
+
timeoutMs: GITHUB_COMMAND_TIMEOUT_MS,
|
|
2237
|
+
isAuthFailureText,
|
|
2238
|
+
errorClasses: {
|
|
2239
|
+
isAlreadyClassified: (candidate) => candidate instanceof GitHubAuthenticationError,
|
|
2240
|
+
isCommandError: (candidate) => candidate instanceof GitHubCommandError,
|
|
2241
|
+
createAuthError: (stderr) => new GitHubAuthenticationError({ stderr }),
|
|
2242
|
+
createMissingError: () => new GitHubCliMissingError(),
|
|
2243
|
+
createCommandError: (params) => new GitHubCommandError(params),
|
|
2244
|
+
},
|
|
2245
|
+
});
|
|
2246
|
+
async function runGhCommand(args, options) {
|
|
2247
|
+
return githubCliRunner.run(args, options);
|
|
2248
|
+
}
|
|
2249
|
+
// Anchored to github.com so a pasted URL from an unrelated tracker (a GitLab
|
|
2250
|
+
// or Gitea link that happens to share the /owner/repo/(pull|issues)/N shape)
|
|
2251
|
+
// passes through as literal search text instead of being misread as a number.
|
|
2252
|
+
// The workspace's resolved GitHub Enterprise host (if any) is accepted too.
|
|
2253
|
+
const GITHUB_COM_ISSUE_OR_PR_URL_PATTERN = /^https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:pull|issues)\/(\d+)(?:[/?#].*)?$/i;
|
|
2254
|
+
function buildEnterpriseIssueOrPrUrlPattern(host) {
|
|
2255
|
+
const escapedHost = host.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2256
|
+
return new RegExp(`^https?://${escapedHost}/[^/\\s]+/[^/\\s]+/(?:pull|issues)/(\\d+)(?:[/?#].*)?$`, "i");
|
|
2257
|
+
}
|
|
2258
|
+
function normalizeGitHubSearchQuery(query, enterpriseHost) {
|
|
2259
|
+
const trimmed = query.trim();
|
|
2260
|
+
const cloudMatch = trimmed.match(GITHUB_COM_ISSUE_OR_PR_URL_PATTERN);
|
|
2261
|
+
if (cloudMatch) {
|
|
2262
|
+
return cloudMatch[1];
|
|
2263
|
+
}
|
|
2264
|
+
if (enterpriseHost) {
|
|
2265
|
+
const enterpriseMatch = trimmed.match(buildEnterpriseIssueOrPrUrlPattern(enterpriseHost));
|
|
2266
|
+
if (enterpriseMatch) {
|
|
2267
|
+
return enterpriseMatch[1];
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
return query;
|
|
2271
|
+
}
|
|
2272
|
+
function buildCacheKey(params) {
|
|
2273
|
+
return `${params.cwd}:${params.method}:${stableStringify(params.args)}`;
|
|
2274
|
+
}
|
|
2275
|
+
function stableStringify(value) {
|
|
2276
|
+
return JSON.stringify(sortJsonValue(value));
|
|
2277
|
+
}
|
|
2278
|
+
function sortJsonValue(value) {
|
|
2279
|
+
if (Array.isArray(value)) {
|
|
2280
|
+
return value.map(sortJsonValue);
|
|
2281
|
+
}
|
|
2282
|
+
if (!value || typeof value !== "object") {
|
|
2283
|
+
return value;
|
|
2284
|
+
}
|
|
2285
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
2286
|
+
const sorted = {};
|
|
2287
|
+
for (const [key, entryValue] of entries) {
|
|
2288
|
+
sorted[key] = sortJsonValue(entryValue);
|
|
2289
|
+
}
|
|
2290
|
+
return sorted;
|
|
2291
|
+
}
|
|
2292
|
+
function parseGitHubJsonOutput(stdout, schema, context) {
|
|
2293
|
+
return parseCliJsonOutput({
|
|
2294
|
+
commandName: "gh",
|
|
2295
|
+
args: context.args,
|
|
2296
|
+
cwd: context.cwd,
|
|
2297
|
+
stdout: stdout || context.emptyFallback,
|
|
2298
|
+
schema,
|
|
2299
|
+
createCommandError: (params) => new GitHubCommandError(params),
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
function isGitHubAuthenticationError(error) {
|
|
2303
|
+
return error instanceof GitHubAuthenticationError;
|
|
2304
|
+
}
|
|
2305
|
+
function isAuthFailureText(text) {
|
|
2306
|
+
const normalized = text.toLowerCase();
|
|
2307
|
+
return (normalized.includes("gh auth login") ||
|
|
2308
|
+
normalized.includes("not logged into any github hosts") ||
|
|
2309
|
+
normalized.includes("authentication failed") ||
|
|
2310
|
+
normalized.includes("authentication required") ||
|
|
2311
|
+
normalized.includes("bad credentials") ||
|
|
2312
|
+
normalized.includes("http 401"));
|
|
2313
|
+
}
|
|
2314
|
+
function isNoPullRequestFoundError(error) {
|
|
2315
|
+
if (!(error instanceof GitHubCommandError)) {
|
|
2316
|
+
return false;
|
|
2317
|
+
}
|
|
2318
|
+
const text = error.stderr.toLowerCase();
|
|
2319
|
+
return text.includes("no pull requests found");
|
|
2320
|
+
}
|
|
2321
|
+
function isStatusCheckRollupPermissionError(error) {
|
|
2322
|
+
if (!(error instanceof GitHubCommandError)) {
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
2325
|
+
return error.stderr.toLowerCase().includes("statuscheckrollup");
|
|
2326
|
+
}
|
|
2327
|
+
function isGitHubRateLimitError(error) {
|
|
2328
|
+
return (error instanceof GitHubCommandError &&
|
|
2329
|
+
/(?:rate limit|rate_limit).*(?:exceed|exhaust)|(?:exceed|exhaust).*rate limit/i.test(error.stderr));
|
|
2330
|
+
}
|
|
2331
|
+
async function resolveCurrentPullRequestView(options) {
|
|
2332
|
+
const viewCandidate = await tryCurrentPullRequestView(options);
|
|
2333
|
+
const viewMatch = viewCandidate
|
|
2334
|
+
? pickPullRequestCandidate({
|
|
2335
|
+
candidates: [viewCandidate],
|
|
2336
|
+
headRef: options.headRef,
|
|
2337
|
+
headSha: options.headSha,
|
|
2338
|
+
headRepositoryOwner: options.headRepositoryOwner,
|
|
2339
|
+
})
|
|
2340
|
+
: null;
|
|
2341
|
+
if (viewMatch) {
|
|
2342
|
+
return viewMatch.status;
|
|
2343
|
+
}
|
|
2344
|
+
let listHeadRef = options.headRef;
|
|
2345
|
+
let listRepo;
|
|
2346
|
+
let headRepositoryOwner = options.headRepositoryOwner;
|
|
2347
|
+
if (!headRepositoryOwner) {
|
|
2348
|
+
const repo = await getGitHubRepoView(options);
|
|
2349
|
+
const forkOwner = repo?.owner?.login;
|
|
2350
|
+
const parentOwner = repo?.parent?.owner?.login;
|
|
2351
|
+
const parentName = repo?.parent?.name;
|
|
2352
|
+
if (!forkOwner) {
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
if (parentOwner && parentName) {
|
|
2356
|
+
listHeadRef = `${forkOwner}:${options.headRef}`;
|
|
2357
|
+
listRepo = `${parentOwner}/${parentName}`;
|
|
2358
|
+
}
|
|
2359
|
+
headRepositoryOwner = forkOwner;
|
|
2360
|
+
}
|
|
2361
|
+
const candidates = await listCurrentPullRequestCandidates({
|
|
2362
|
+
cwd: options.cwd,
|
|
2363
|
+
headRef: listHeadRef,
|
|
2364
|
+
run: options.run,
|
|
2365
|
+
repo: listRepo,
|
|
2366
|
+
});
|
|
2367
|
+
const match = pickPullRequestCandidate({
|
|
2368
|
+
candidates,
|
|
2369
|
+
headRef: options.headRef,
|
|
2370
|
+
headSha: options.headSha,
|
|
2371
|
+
headRepositoryOwner,
|
|
2372
|
+
});
|
|
2373
|
+
return match?.status ?? null;
|
|
2374
|
+
}
|
|
2375
|
+
async function addCurrentPullRequestGithubFacts(options) {
|
|
2376
|
+
const { status } = options;
|
|
2377
|
+
if (!status?.repoOwner || !status.repoName || typeof status.number !== "number") {
|
|
2378
|
+
return status;
|
|
2379
|
+
}
|
|
2380
|
+
const facts = await loadPullRequestGithubFacts({
|
|
2381
|
+
cwd: options.cwd,
|
|
2382
|
+
owner: status.repoOwner,
|
|
2383
|
+
name: status.repoName,
|
|
2384
|
+
number: status.number,
|
|
2385
|
+
run: options.run,
|
|
2386
|
+
});
|
|
2387
|
+
if (!facts) {
|
|
2388
|
+
return status;
|
|
2389
|
+
}
|
|
2390
|
+
return {
|
|
2391
|
+
...status,
|
|
2392
|
+
forgeSpecific: { forge: "github", ...facts },
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
async function loadPullRequestGithubFacts(options) {
|
|
2396
|
+
const args = [
|
|
2397
|
+
"api",
|
|
2398
|
+
"graphql",
|
|
2399
|
+
"-f",
|
|
2400
|
+
`query=${PULL_REQUEST_STATUS_FACTS_QUERY}`,
|
|
2401
|
+
"-F",
|
|
2402
|
+
`owner=${options.owner}`,
|
|
2403
|
+
"-F",
|
|
2404
|
+
`name=${options.name}`,
|
|
2405
|
+
"-F",
|
|
2406
|
+
`number=${options.number}`,
|
|
2407
|
+
];
|
|
2408
|
+
try {
|
|
2409
|
+
const stdout = await options.run(args, { cwd: options.cwd });
|
|
2410
|
+
return parsePullRequestGithubFacts(stdout, { args, cwd: options.cwd });
|
|
2411
|
+
}
|
|
2412
|
+
catch (error) {
|
|
2413
|
+
if (error instanceof GitHubCommandError) {
|
|
2414
|
+
return null;
|
|
2415
|
+
}
|
|
2416
|
+
throw error;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
async function tryCurrentPullRequestView(options) {
|
|
2420
|
+
try {
|
|
2421
|
+
const stdout = await runCurrentPullRequestStatusCommand({
|
|
2422
|
+
cwd: options.cwd,
|
|
2423
|
+
run: options.run,
|
|
2424
|
+
args: ["pr", "view"],
|
|
2425
|
+
});
|
|
2426
|
+
return parseCurrentPullRequestCandidate(stdout, options.headRef, {
|
|
2427
|
+
args: ["pr", "view", "--json", CURRENT_PR_STATUS_FIELDS],
|
|
2428
|
+
cwd: options.cwd,
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
catch (error) {
|
|
2432
|
+
if (isNoPullRequestFoundError(error)) {
|
|
2433
|
+
return null;
|
|
2434
|
+
}
|
|
2435
|
+
throw error;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
async function listCurrentPullRequestCandidates(options) {
|
|
2439
|
+
const args = ["pr", "list"];
|
|
2440
|
+
if (options.repo) {
|
|
2441
|
+
args.push("--repo", options.repo);
|
|
2442
|
+
}
|
|
2443
|
+
args.push("--state", "all", "--head", options.headRef, "--limit", "10");
|
|
2444
|
+
try {
|
|
2445
|
+
const stdout = await runCurrentPullRequestStatusCommand({
|
|
2446
|
+
cwd: options.cwd,
|
|
2447
|
+
run: options.run,
|
|
2448
|
+
args,
|
|
2449
|
+
});
|
|
2450
|
+
return parseCurrentPullRequestCandidateList(stdout, options.headRef, {
|
|
2451
|
+
args: [...args, "--json", CURRENT_PR_STATUS_FIELDS],
|
|
2452
|
+
cwd: options.cwd,
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
catch (error) {
|
|
2456
|
+
if (isNoPullRequestFoundError(error)) {
|
|
2457
|
+
return [];
|
|
2458
|
+
}
|
|
2459
|
+
throw error;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
async function runCurrentPullRequestStatusCommand(options) {
|
|
2463
|
+
try {
|
|
2464
|
+
return await options.run([...options.args, "--json", CURRENT_PR_STATUS_FIELDS], {
|
|
2465
|
+
cwd: options.cwd,
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
catch (error) {
|
|
2469
|
+
if (!isStatusCheckRollupPermissionError(error)) {
|
|
2470
|
+
throw error;
|
|
2471
|
+
}
|
|
2472
|
+
return options.run([...options.args, "--json", CURRENT_PR_STATUS_BASE_FIELDS], {
|
|
2473
|
+
cwd: options.cwd,
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
async function resolveGitHubSlugFromOrigin(cwd) {
|
|
2478
|
+
let stdout;
|
|
2479
|
+
try {
|
|
2480
|
+
({ stdout } = await runGitCommand(["config", "--get", "remote.origin.url"], {
|
|
2481
|
+
cwd,
|
|
2482
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
2483
|
+
timeout: GIT_ORIGIN_URL_READ_TIMEOUT_MS,
|
|
2484
|
+
}));
|
|
2485
|
+
}
|
|
2486
|
+
catch {
|
|
2487
|
+
return null;
|
|
2488
|
+
}
|
|
2489
|
+
return parseGitHubRemoteUrl(stdout.trim())?.repo ?? null;
|
|
2490
|
+
}
|
|
2491
|
+
async function getGitHubRepoView(options) {
|
|
2492
|
+
const args = ["repo", "view", "--json", "owner,name,parent"];
|
|
2493
|
+
try {
|
|
2494
|
+
const stdout = await options.run(args, {
|
|
2495
|
+
cwd: options.cwd,
|
|
2496
|
+
});
|
|
2497
|
+
return parseGitHubJsonOutput(stdout, GitHubRepoViewSchema, {
|
|
2498
|
+
args,
|
|
2499
|
+
cwd: options.cwd,
|
|
2500
|
+
emptyFallback: "{}",
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
catch (error) {
|
|
2504
|
+
// A missing CLI or an auth failure must surface as its typed class so the
|
|
2505
|
+
// caller reports the real problem; only a genuine "not a resolvable repo"
|
|
2506
|
+
// (gh command failure / malformed output) degrades to null.
|
|
2507
|
+
if (error instanceof GitHubEnterpriseHostProbeError ||
|
|
2508
|
+
error instanceof GitHubCliMissingError ||
|
|
2509
|
+
isGitHubAuthenticationError(error)) {
|
|
2510
|
+
throw error;
|
|
2511
|
+
}
|
|
2512
|
+
return null;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
function parseCurrentPullRequestCandidate(stdout, fallbackHeadRefName, context) {
|
|
2516
|
+
const item = parseGitHubJsonOutput(stdout, CurrentPullRequestStatusSchema, {
|
|
2517
|
+
...context,
|
|
2518
|
+
emptyFallback: "{}",
|
|
2519
|
+
});
|
|
2520
|
+
return toCurrentPullRequestCandidate(item, fallbackHeadRefName);
|
|
2521
|
+
}
|
|
2522
|
+
function parseCurrentPullRequestCandidateList(stdout, fallbackHeadRefName, context) {
|
|
2523
|
+
const items = parseGitHubJsonOutput(stdout, z.array(CurrentPullRequestStatusSchema), {
|
|
2524
|
+
...context,
|
|
2525
|
+
emptyFallback: "[]",
|
|
2526
|
+
});
|
|
2527
|
+
return items
|
|
2528
|
+
.map((item) => toCurrentPullRequestCandidate(item, fallbackHeadRefName))
|
|
2529
|
+
.filter((candidate) => candidate !== null);
|
|
2530
|
+
}
|
|
2531
|
+
function parsePullRequestGithubFacts(stdout, context) {
|
|
2532
|
+
const parsed = parseGitHubJsonOutput(stdout, GitHubPullRequestFactsGraphqlSchema, {
|
|
2533
|
+
...context,
|
|
2534
|
+
emptyFallback: "{}",
|
|
2535
|
+
});
|
|
2536
|
+
const repository = parsed.data.repository;
|
|
2537
|
+
const pullRequest = repository?.pullRequest;
|
|
2538
|
+
if (!repository || !pullRequest) {
|
|
2539
|
+
return null;
|
|
2540
|
+
}
|
|
2541
|
+
return {
|
|
2542
|
+
mergeStateStatus: pullRequest.mergeStateStatus ?? null,
|
|
2543
|
+
autoMergeRequest: toGitHubAutoMergeRequest(pullRequest.autoMergeRequest),
|
|
2544
|
+
viewerCanEnableAutoMerge: pullRequest.viewerCanEnableAutoMerge ?? false,
|
|
2545
|
+
viewerCanDisableAutoMerge: pullRequest.viewerCanDisableAutoMerge ?? false,
|
|
2546
|
+
viewerCanMergeAsAdmin: pullRequest.viewerCanMergeAsAdmin ?? false,
|
|
2547
|
+
viewerCanUpdateBranch: pullRequest.viewerCanUpdateBranch ?? false,
|
|
2548
|
+
repository: toGitHubRepositoryMergePolicy(repository),
|
|
2549
|
+
isMergeQueueEnabled: pullRequest.isMergeQueueEnabled ?? false,
|
|
2550
|
+
isInMergeQueue: pullRequest.isInMergeQueue ?? false,
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
function toGitHubAutoMergeRequest(request) {
|
|
2554
|
+
if (!request) {
|
|
2555
|
+
return null;
|
|
2556
|
+
}
|
|
2557
|
+
return {
|
|
2558
|
+
enabledAt: request.enabledAt ?? null,
|
|
2559
|
+
mergeMethod: request.mergeMethod ?? null,
|
|
2560
|
+
enabledBy: request.enabledBy?.login ?? null,
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
function toGitHubRepositoryMergePolicy(repository) {
|
|
2564
|
+
return {
|
|
2565
|
+
autoMergeAllowed: repository.autoMergeAllowed ?? false,
|
|
2566
|
+
mergeCommitAllowed: repository.mergeCommitAllowed ?? false,
|
|
2567
|
+
squashMergeAllowed: repository.squashMergeAllowed ?? false,
|
|
2568
|
+
rebaseMergeAllowed: repository.rebaseMergeAllowed ?? false,
|
|
2569
|
+
viewerDefaultMergeMethod: repository.viewerDefaultMergeMethod ?? null,
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
// Reshape a batched GraphQL PR node into the gh CLI JSON shape so candidate
|
|
2573
|
+
// picking and status construction stay shared with the per-target path.
|
|
2574
|
+
function toBatchCurrentPullRequestItem(node, statusCheckRollup) {
|
|
2575
|
+
return {
|
|
2576
|
+
number: node.number,
|
|
2577
|
+
url: node.url,
|
|
2578
|
+
title: node.title,
|
|
2579
|
+
state: node.state,
|
|
2580
|
+
isDraft: node.isDraft ?? false,
|
|
2581
|
+
baseRefName: node.baseRefName,
|
|
2582
|
+
headRefName: node.headRefName,
|
|
2583
|
+
...(node.headRefOid ? { headRefOid: node.headRefOid } : {}),
|
|
2584
|
+
mergedAt: node.mergedAt ?? null,
|
|
2585
|
+
...(statusCheckRollup ? { statusCheckRollup } : {}),
|
|
2586
|
+
reviewDecision: node.reviewDecision,
|
|
2587
|
+
mergeable: node.mergeable,
|
|
2588
|
+
headRepositoryOwner: node.headRepositoryOwner,
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
function toCurrentPullRequestCandidate(item, fallbackHeadRefName) {
|
|
2592
|
+
const status = toCurrentPullRequestStatus(item, fallbackHeadRefName);
|
|
2593
|
+
if (!status) {
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
const headRepositoryOwner = item.headRepositoryOwner?.login;
|
|
2597
|
+
const headSha = item.headRefOid;
|
|
2598
|
+
return {
|
|
2599
|
+
status,
|
|
2600
|
+
...(headSha ? { headSha } : {}),
|
|
2601
|
+
...(headRepositoryOwner ? { headRepositoryOwner } : {}),
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function isCandidateForHeadRef(candidate, headRef) {
|
|
2605
|
+
return candidate.status.headRefName === headRef && hasResolvedRepoIdentity(candidate.status);
|
|
2606
|
+
}
|
|
2607
|
+
function hasResolvedRepoIdentity(status) {
|
|
2608
|
+
return Boolean(status.repoOwner && status.repoName);
|
|
2609
|
+
}
|
|
2610
|
+
function pickPullRequestCandidate(options) {
|
|
2611
|
+
const matching = options.candidates.filter((candidate) => {
|
|
2612
|
+
if (!isCandidateForHeadRef(candidate, options.headRef)) {
|
|
2613
|
+
return false;
|
|
2614
|
+
}
|
|
2615
|
+
if (candidate.status.state !== "open" &&
|
|
2616
|
+
(!options.headSha || candidate.headSha !== options.headSha)) {
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
if (!options.headRepositoryOwner) {
|
|
2620
|
+
return true;
|
|
2621
|
+
}
|
|
2622
|
+
return candidate.headRepositoryOwner === options.headRepositoryOwner;
|
|
2623
|
+
});
|
|
2624
|
+
matching.sort((left, right) => comparePullRequestCandidatePreference(left, right, options.headSha));
|
|
2625
|
+
return matching[0] ?? null;
|
|
2626
|
+
}
|
|
2627
|
+
function comparePullRequestCandidatePreference(left, right, headSha) {
|
|
2628
|
+
const stateRank = getPullRequestStateRank(left.status) - getPullRequestStateRank(right.status);
|
|
2629
|
+
if (stateRank !== 0) {
|
|
2630
|
+
return stateRank;
|
|
2631
|
+
}
|
|
2632
|
+
const leftExact = headSha !== undefined && left.headSha === headSha;
|
|
2633
|
+
const rightExact = headSha !== undefined && right.headSha === headSha;
|
|
2634
|
+
if (leftExact !== rightExact) {
|
|
2635
|
+
return leftExact ? -1 : 1;
|
|
2636
|
+
}
|
|
2637
|
+
return 0;
|
|
2638
|
+
}
|
|
2639
|
+
function getPullRequestStateRank(status) {
|
|
2640
|
+
if (status.state === "open" || status.isDraft) {
|
|
2641
|
+
return 0;
|
|
2642
|
+
}
|
|
2643
|
+
if (status.state === "merged") {
|
|
2644
|
+
return 1;
|
|
2645
|
+
}
|
|
2646
|
+
return 2;
|
|
2647
|
+
}
|
|
2648
|
+
async function resolveConfiguredCloneProtocol(cwd, run) {
|
|
2649
|
+
try {
|
|
2650
|
+
const protocol = (await run(["config", "get", "git_protocol", "--host", "github.com"], {
|
|
2651
|
+
cwd,
|
|
2652
|
+
}))
|
|
2653
|
+
.trim()
|
|
2654
|
+
.toLowerCase();
|
|
2655
|
+
return protocol === "ssh" ? "ssh" : "https";
|
|
2656
|
+
}
|
|
2657
|
+
catch (error) {
|
|
2658
|
+
if (error instanceof GitHubCommandError) {
|
|
2659
|
+
return "https";
|
|
2660
|
+
}
|
|
2661
|
+
throw error;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
function parseRepositoryList(stdout, cloneProtocol) {
|
|
2665
|
+
const parsed = z.array(GitHubRepositoryListItemSchema).parse(JSON.parse(stdout || "[]"));
|
|
2666
|
+
return parsed.map((repository) => normalizeRepositorySummary({
|
|
2667
|
+
...repository,
|
|
2668
|
+
nameWithOwner: repository.nameWithOwner,
|
|
2669
|
+
cloneUrl: cloneProtocol === "ssh" ? repository.sshUrl : repository.url,
|
|
2670
|
+
}));
|
|
2671
|
+
}
|
|
2672
|
+
function parseRepositorySearch(stdout, cloneProtocol) {
|
|
2673
|
+
const parsed = z.array(GitHubRepositorySearchItemSchema).parse(JSON.parse(stdout || "[]"));
|
|
2674
|
+
return parsed.map((repository) => normalizeRepositorySummary({
|
|
2675
|
+
...repository,
|
|
2676
|
+
nameWithOwner: repository.fullName,
|
|
2677
|
+
cloneUrl: cloneProtocol === "ssh" ? `git@github.com:${repository.fullName}.git` : repository.url,
|
|
2678
|
+
}));
|
|
2679
|
+
}
|
|
2680
|
+
function normalizeRepositorySummary(repository) {
|
|
2681
|
+
const nameWithOwner = repository.nameWithOwner.trim();
|
|
2682
|
+
if (!nameWithOwner.includes("/")) {
|
|
2683
|
+
throw new Error(`GitHub repository is missing owner identity: ${nameWithOwner}`);
|
|
2684
|
+
}
|
|
2685
|
+
return {
|
|
2686
|
+
id: String(repository.id).trim(),
|
|
2687
|
+
name: repository.name.trim(),
|
|
2688
|
+
nameWithOwner,
|
|
2689
|
+
description: repository.description ?? null,
|
|
2690
|
+
// We query isPrivate, not visibility: `gh repo list --json visibility` is unsupported before
|
|
2691
|
+
// gh 2.28 (Debian Bookworm ships 2.23), while isPrivate works on every version. The wire schema
|
|
2692
|
+
// requires the visibility enum, so old clients still get a value they can parse.
|
|
2693
|
+
visibility: repository.isPrivate ? "private" : "public",
|
|
2694
|
+
updatedAt: repository.updatedAt,
|
|
2695
|
+
cloneUrl: repository.cloneUrl.trim(),
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
function toPullRequestCheckoutTarget(parsed) {
|
|
2699
|
+
const pullRequest = parsed.data.repository.pullRequest;
|
|
2700
|
+
if (!pullRequest) {
|
|
2701
|
+
throw new Error("Pull request not found");
|
|
2702
|
+
}
|
|
2703
|
+
return {
|
|
2704
|
+
number: pullRequest.number,
|
|
2705
|
+
baseRefName: pullRequest.baseRefName,
|
|
2706
|
+
headRefName: pullRequest.headRefName,
|
|
2707
|
+
checkoutRefs: [
|
|
2708
|
+
{ remoteName: "origin", remoteRef: `refs/pull/${pullRequest.number}/head` },
|
|
2709
|
+
{ remoteName: "upstream", remoteRef: `refs/pull/${pullRequest.number}/head` },
|
|
2710
|
+
],
|
|
2711
|
+
headOwnerLogin: pullRequest.headRepositoryOwner?.login || null,
|
|
2712
|
+
headRepositorySshUrl: pullRequest.headRepository?.sshUrl || null,
|
|
2713
|
+
headRepositoryUrl: pullRequest.headRepository?.url || null,
|
|
2714
|
+
isCrossRepository: pullRequest.isCrossRepository,
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
function toPullRequestSummary(item) {
|
|
2718
|
+
return {
|
|
2719
|
+
number: item.number,
|
|
2720
|
+
title: item.title,
|
|
2721
|
+
url: item.url,
|
|
2722
|
+
state: item.state,
|
|
2723
|
+
body: item.body,
|
|
2724
|
+
baseRefName: item.baseRefName,
|
|
2725
|
+
headRefName: item.headRefName,
|
|
2726
|
+
labels: item.labels.map((label) => label.name ?? "").filter((name) => name.length > 0),
|
|
2727
|
+
updatedAt: item.updatedAt,
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
function toIssueSummary(item) {
|
|
2731
|
+
return {
|
|
2732
|
+
number: item.number,
|
|
2733
|
+
title: item.title,
|
|
2734
|
+
url: item.url,
|
|
2735
|
+
state: item.state,
|
|
2736
|
+
body: item.body,
|
|
2737
|
+
labels: item.labels.map((label) => label.name ?? "").filter((name) => name.length > 0),
|
|
2738
|
+
updatedAt: item.updatedAt,
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
function toPullRequestTimeline(parsed, identity) {
|
|
2742
|
+
const pullRequest = parsed.data?.repository?.pullRequest;
|
|
2743
|
+
const reviewThreadItems = pullRequest
|
|
2744
|
+
? pullRequest.reviewThreads.nodes.flatMap(toPullRequestTimelineReviewThreadItems)
|
|
2745
|
+
: [];
|
|
2746
|
+
const reviewThreadItemIds = new Set(reviewThreadItems.map((item) => item.id).filter((id) => id.length > 0));
|
|
2747
|
+
const items = pullRequest
|
|
2748
|
+
? [
|
|
2749
|
+
...pullRequest.reviews.nodes.flatMap(toPullRequestTimelineReviewItem),
|
|
2750
|
+
...pullRequest.comments.nodes
|
|
2751
|
+
.filter((comment) => !reviewThreadItemIds.has(comment.id))
|
|
2752
|
+
.map(toPullRequestTimelineCommentItem),
|
|
2753
|
+
...reviewThreadItems,
|
|
2754
|
+
].sort(compareTimelineItems)
|
|
2755
|
+
: [];
|
|
2756
|
+
return {
|
|
2757
|
+
prNumber: pullRequest?.number ?? identity.prNumber,
|
|
2758
|
+
repoOwner: identity.repoOwner,
|
|
2759
|
+
repoName: identity.repoName,
|
|
2760
|
+
items,
|
|
2761
|
+
// S3 deliberately caps timeline fetches at the first 100 reviews, comments, and review threads.
|
|
2762
|
+
truncated: Boolean(pullRequest?.reviews.pageInfo.hasNextPage ||
|
|
2763
|
+
pullRequest?.comments.pageInfo.hasNextPage ||
|
|
2764
|
+
pullRequest?.reviewThreads.pageInfo.hasNextPage ||
|
|
2765
|
+
pullRequest?.reviewThreads.nodes.some((thread) => thread.comments.pageInfo.hasNextPage)),
|
|
2766
|
+
error: pullRequest ? null : { kind: "not_found", message: "Pull request not found" },
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
function toPullRequestTimelineReviewItem(review) {
|
|
2770
|
+
const reviewState = mapTimelineReviewState(review.state, review.body ?? "");
|
|
2771
|
+
if (!reviewState) {
|
|
2772
|
+
return [];
|
|
2773
|
+
}
|
|
2774
|
+
return [
|
|
2775
|
+
{
|
|
2776
|
+
kind: "review",
|
|
2777
|
+
id: review.id,
|
|
2778
|
+
author: review.author?.login ?? "unknown",
|
|
2779
|
+
authorUrl: review.author?.url ?? null,
|
|
2780
|
+
avatarUrl: review.author?.avatarUrl ?? null,
|
|
2781
|
+
body: normalizeGitHubTimelineBody(review.body ?? "", review.bodyHTML ?? ""),
|
|
2782
|
+
createdAt: parseOptionalTime(review.submittedAt ?? null),
|
|
2783
|
+
url: review.url,
|
|
2784
|
+
reviewState,
|
|
2785
|
+
},
|
|
2786
|
+
];
|
|
2787
|
+
}
|
|
2788
|
+
function toPullRequestTimelineCommentItem(comment) {
|
|
2789
|
+
return {
|
|
2790
|
+
kind: "comment",
|
|
2791
|
+
id: comment.id,
|
|
2792
|
+
author: comment.author?.login ?? "unknown",
|
|
2793
|
+
authorUrl: comment.author?.url ?? null,
|
|
2794
|
+
avatarUrl: comment.author?.avatarUrl ?? null,
|
|
2795
|
+
body: normalizeGitHubTimelineBody(comment.body ?? "", comment.bodyHTML ?? ""),
|
|
2796
|
+
createdAt: parseOptionalTime(comment.createdAt ?? null),
|
|
2797
|
+
url: comment.url,
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
const RAW_MARKDOWN_IMAGE_RE = /!\[[^\]]*\]\(\s*([^\s)]+)(?:\s+["'][^)]*["'])?\s*\)/g;
|
|
2801
|
+
const HTML_IMAGE_RE = /<img\b[^>]*\bsrc\s*=\s*(["'])(.*?)\1[^>]*>/gi;
|
|
2802
|
+
const GITHUB_RENDERED_IMAGE_HOSTS = new Set([
|
|
2803
|
+
"camo.githubusercontent.com",
|
|
2804
|
+
"private-user-images.githubusercontent.com",
|
|
2805
|
+
]);
|
|
2806
|
+
function normalizeGitHubTimelineBody(body, bodyHTML) {
|
|
2807
|
+
const rawImages = extractRawImageSourceReferences(body);
|
|
2808
|
+
if (rawImages.length === 0) {
|
|
2809
|
+
return body;
|
|
2810
|
+
}
|
|
2811
|
+
const renderedSources = extractRenderedImageSources(bodyHTML);
|
|
2812
|
+
if (renderedSources.length !== rawImages.length) {
|
|
2813
|
+
return body;
|
|
2814
|
+
}
|
|
2815
|
+
let cursor = 0;
|
|
2816
|
+
let normalized = "";
|
|
2817
|
+
for (let index = 0; index < rawImages.length; index += 1) {
|
|
2818
|
+
const rawImage = rawImages[index];
|
|
2819
|
+
const renderedSrc = renderedSources[index];
|
|
2820
|
+
if (!rawImage ||
|
|
2821
|
+
!renderedSrc ||
|
|
2822
|
+
!isRawGitHubAttachmentSource(rawImage.src) ||
|
|
2823
|
+
!isGitHubRenderedImageSource(renderedSrc)) {
|
|
2824
|
+
return body;
|
|
2825
|
+
}
|
|
2826
|
+
normalized += body.slice(cursor, rawImage.start);
|
|
2827
|
+
normalized += renderedSrc;
|
|
2828
|
+
cursor = rawImage.end;
|
|
2829
|
+
}
|
|
2830
|
+
normalized += body.slice(cursor);
|
|
2831
|
+
return normalized;
|
|
2832
|
+
}
|
|
2833
|
+
function extractRawImageSourceReferences(source) {
|
|
2834
|
+
const references = [
|
|
2835
|
+
...extractHtmlImageSourceReferences(source),
|
|
2836
|
+
...extractMarkdownImageSourceReferences(source),
|
|
2837
|
+
];
|
|
2838
|
+
return references.sort((left, right) => left.start - right.start);
|
|
2839
|
+
}
|
|
2840
|
+
function extractRenderedImageSources(source) {
|
|
2841
|
+
return extractHtmlImageSourceReferences(source).map((reference) => reference.src);
|
|
2842
|
+
}
|
|
2843
|
+
function extractHtmlImageSourceReferences(source) {
|
|
2844
|
+
const references = [];
|
|
2845
|
+
HTML_IMAGE_RE.lastIndex = 0;
|
|
2846
|
+
let match;
|
|
2847
|
+
while ((match = HTML_IMAGE_RE.exec(source)) !== null) {
|
|
2848
|
+
const src = decodeHtmlAttribute(match[2] ?? "");
|
|
2849
|
+
if (!src) {
|
|
2850
|
+
continue;
|
|
2851
|
+
}
|
|
2852
|
+
const rawAttributeSrc = match[2] ?? "";
|
|
2853
|
+
const start = match.index + match[0].indexOf(rawAttributeSrc);
|
|
2854
|
+
references.push({ src, start, end: start + rawAttributeSrc.length });
|
|
2855
|
+
}
|
|
2856
|
+
return references;
|
|
2857
|
+
}
|
|
2858
|
+
function extractMarkdownImageSourceReferences(source) {
|
|
2859
|
+
const references = [];
|
|
2860
|
+
RAW_MARKDOWN_IMAGE_RE.lastIndex = 0;
|
|
2861
|
+
let match;
|
|
2862
|
+
while ((match = RAW_MARKDOWN_IMAGE_RE.exec(source)) !== null) {
|
|
2863
|
+
const src = match[1] ?? "";
|
|
2864
|
+
if (!src) {
|
|
2865
|
+
continue;
|
|
2866
|
+
}
|
|
2867
|
+
const start = match.index + match[0].indexOf(src);
|
|
2868
|
+
references.push({ src, start, end: start + src.length });
|
|
2869
|
+
}
|
|
2870
|
+
return references;
|
|
2871
|
+
}
|
|
2872
|
+
function isRawGitHubAttachmentSource(src) {
|
|
2873
|
+
try {
|
|
2874
|
+
const url = new URL(src);
|
|
2875
|
+
return (url.protocol === "https:" &&
|
|
2876
|
+
url.hostname === "github.com" &&
|
|
2877
|
+
url.pathname.startsWith("/user-attachments/assets/"));
|
|
2878
|
+
}
|
|
2879
|
+
catch {
|
|
2880
|
+
return false;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
function isGitHubRenderedImageSource(src) {
|
|
2884
|
+
try {
|
|
2885
|
+
const url = new URL(src);
|
|
2886
|
+
return url.protocol === "https:" && GITHUB_RENDERED_IMAGE_HOSTS.has(url.hostname);
|
|
2887
|
+
}
|
|
2888
|
+
catch {
|
|
2889
|
+
return false;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
function decodeHtmlAttribute(value) {
|
|
2893
|
+
return value
|
|
2894
|
+
.replaceAll("&", "&")
|
|
2895
|
+
.replaceAll(""", '"')
|
|
2896
|
+
.replaceAll("'", "'")
|
|
2897
|
+
.replaceAll("<", "<")
|
|
2898
|
+
.replaceAll(">", ">");
|
|
2899
|
+
}
|
|
2900
|
+
function toPullRequestTimelineReviewThreadItems(thread) {
|
|
2901
|
+
return thread.comments.nodes.map((comment) => ({
|
|
2902
|
+
...toPullRequestTimelineCommentItem(comment),
|
|
2903
|
+
...(comment.pullRequestReview?.id ? { reviewId: comment.pullRequestReview.id } : {}),
|
|
2904
|
+
location: {
|
|
2905
|
+
path: thread.path,
|
|
2906
|
+
...(thread.line !== null && thread.line !== undefined ? { line: thread.line } : {}),
|
|
2907
|
+
...(thread.startLine !== null && thread.startLine !== undefined
|
|
2908
|
+
? { startLine: thread.startLine }
|
|
2909
|
+
: {}),
|
|
2910
|
+
...(thread.id ? { threadId: thread.id } : {}),
|
|
2911
|
+
isResolved: thread.isResolved,
|
|
2912
|
+
isOutdated: thread.isOutdated,
|
|
2913
|
+
},
|
|
2914
|
+
}));
|
|
2915
|
+
}
|
|
2916
|
+
function toGitHubCheckRunDetails(parsed) {
|
|
2917
|
+
return {
|
|
2918
|
+
checkRunId: parsed.id,
|
|
2919
|
+
workflowRunId: parsed.check_suite?.workflow_run?.id ?? null,
|
|
2920
|
+
name: parsed.name,
|
|
2921
|
+
status: parsed.status,
|
|
2922
|
+
conclusion: parsed.conclusion,
|
|
2923
|
+
url: parsed.html_url,
|
|
2924
|
+
detailsUrl: parsed.details_url,
|
|
2925
|
+
output: parsed.output,
|
|
2926
|
+
annotations: [],
|
|
2927
|
+
failedJobs: [],
|
|
2928
|
+
truncated: false,
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
function toGitHubCheckAnnotations(annotations) {
|
|
2932
|
+
return annotations.map((annotation) => {
|
|
2933
|
+
const result = {};
|
|
2934
|
+
if (annotation.path)
|
|
2935
|
+
result.path = annotation.path;
|
|
2936
|
+
if (annotation.start_line !== undefined)
|
|
2937
|
+
result.startLine = annotation.start_line;
|
|
2938
|
+
if (annotation.end_line !== undefined)
|
|
2939
|
+
result.endLine = annotation.end_line;
|
|
2940
|
+
if (annotation.annotation_level)
|
|
2941
|
+
result.annotationLevel = annotation.annotation_level;
|
|
2942
|
+
if (annotation.message)
|
|
2943
|
+
result.message = annotation.message;
|
|
2944
|
+
if (annotation.title)
|
|
2945
|
+
result.title = annotation.title;
|
|
2946
|
+
if (annotation.raw_details)
|
|
2947
|
+
result.rawDetails = annotation.raw_details;
|
|
2948
|
+
return result;
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
function toGitHubActionsJobs(parsed) {
|
|
2952
|
+
return parsed.jobs.map((job) => {
|
|
2953
|
+
const result = {
|
|
2954
|
+
jobId: job.id,
|
|
2955
|
+
name: job.name,
|
|
2956
|
+
status: job.status,
|
|
2957
|
+
conclusion: job.conclusion,
|
|
2958
|
+
url: job.html_url,
|
|
2959
|
+
};
|
|
2960
|
+
if (job.completed_at)
|
|
2961
|
+
result.completedAt = job.completed_at;
|
|
2962
|
+
return result;
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
function isFailedActionsJob(job) {
|
|
2966
|
+
return (job.conclusion === "failure" ||
|
|
2967
|
+
job.conclusion === "cancelled" ||
|
|
2968
|
+
job.conclusion === "timed_out" ||
|
|
2969
|
+
job.conclusion === "action_required");
|
|
2970
|
+
}
|
|
2971
|
+
async function getCachedCheckLogTail(input) {
|
|
2972
|
+
const key = `${input.job.jobId}:${input.job.completedAt ?? ""}`;
|
|
2973
|
+
const cached = input.cache.get(key);
|
|
2974
|
+
if (cached) {
|
|
2975
|
+
input.cache.delete(key);
|
|
2976
|
+
input.cache.set(key, cached);
|
|
2977
|
+
return cached;
|
|
2978
|
+
}
|
|
2979
|
+
const capped = capCheckLogTail(await input.run(["api", `${input.repoPath}/actions/jobs/${input.job.jobId}/logs`], {
|
|
2980
|
+
cwd: input.cwd,
|
|
2981
|
+
}));
|
|
2982
|
+
input.cache.set(key, capped);
|
|
2983
|
+
while (input.cache.size > CHECK_LOG_TAIL_CACHE_MAX_ENTRIES) {
|
|
2984
|
+
const oldestKey = input.cache.keys().next().value;
|
|
2985
|
+
if (!oldestKey) {
|
|
2986
|
+
break;
|
|
2987
|
+
}
|
|
2988
|
+
input.cache.delete(oldestKey);
|
|
2989
|
+
}
|
|
2990
|
+
return capped;
|
|
2991
|
+
}
|
|
2992
|
+
function capCheckLogTail(log) {
|
|
2993
|
+
const lines = log.split("\n");
|
|
2994
|
+
let truncated = lines.length > CHECK_LOG_TAIL_MAX_LINES;
|
|
2995
|
+
let tail = lines.slice(-CHECK_LOG_TAIL_MAX_LINES).join("\n");
|
|
2996
|
+
if (Buffer.byteLength(tail, "utf8") > CHECK_LOG_TAIL_MAX_BYTES) {
|
|
2997
|
+
truncated = true;
|
|
2998
|
+
tail = utf8SuffixWithinBytes(tail, CHECK_LOG_TAIL_MAX_BYTES);
|
|
2999
|
+
}
|
|
3000
|
+
return { logTail: tail, logTruncated: truncated };
|
|
3001
|
+
}
|
|
3002
|
+
function utf8SuffixWithinBytes(value, maxBytes) {
|
|
3003
|
+
let lowerBound = 0;
|
|
3004
|
+
let upperBound = value.length;
|
|
3005
|
+
while (lowerBound < upperBound) {
|
|
3006
|
+
const midpoint = Math.floor((lowerBound + upperBound) / 2);
|
|
3007
|
+
if (Buffer.byteLength(value.slice(midpoint), "utf8") > maxBytes) {
|
|
3008
|
+
lowerBound = midpoint + 1;
|
|
3009
|
+
}
|
|
3010
|
+
else {
|
|
3011
|
+
upperBound = midpoint;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
return value.slice(lowerBound);
|
|
3015
|
+
}
|
|
3016
|
+
function mapTimelineReviewState(state, body) {
|
|
3017
|
+
switch (state) {
|
|
3018
|
+
case "APPROVED":
|
|
3019
|
+
return "approved";
|
|
3020
|
+
case "CHANGES_REQUESTED":
|
|
3021
|
+
return "changes_requested";
|
|
3022
|
+
case "COMMENTED":
|
|
3023
|
+
return "commented";
|
|
3024
|
+
case "DISMISSED":
|
|
3025
|
+
case "PENDING":
|
|
3026
|
+
return body.trim().length > 0 ? "commented" : null;
|
|
3027
|
+
default:
|
|
3028
|
+
return body.trim().length > 0 ? "commented" : null;
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
function mapPullRequestTimelineError(error) {
|
|
3032
|
+
if (error instanceof GitHubCommandError) {
|
|
3033
|
+
return {
|
|
3034
|
+
kind: classifyPullRequestTimelineError(error.stderr),
|
|
3035
|
+
message: error.stderr || error.message,
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
if (error instanceof GitHubAuthenticationError) {
|
|
3039
|
+
return {
|
|
3040
|
+
kind: "forbidden",
|
|
3041
|
+
message: error.stderr || error.message,
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
return {
|
|
3045
|
+
kind: "unknown",
|
|
3046
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
function classifyPullRequestTimelineError(stderr) {
|
|
3050
|
+
const normalized = stderr.toLowerCase();
|
|
3051
|
+
if (normalized.includes("could not resolve to a pullrequest") ||
|
|
3052
|
+
normalized.includes("pull request not found") ||
|
|
3053
|
+
normalized.includes("pullrequest not found")) {
|
|
3054
|
+
return "not_found";
|
|
3055
|
+
}
|
|
3056
|
+
if (normalized.includes("forbidden") ||
|
|
3057
|
+
normalized.includes("resource not accessible") ||
|
|
3058
|
+
normalized.includes("permission") ||
|
|
3059
|
+
normalized.includes("access denied") ||
|
|
3060
|
+
normalized.includes("requires authentication") ||
|
|
3061
|
+
normalized.includes("http 403")) {
|
|
3062
|
+
return "forbidden";
|
|
3063
|
+
}
|
|
3064
|
+
return "unknown";
|
|
3065
|
+
}
|
|
3066
|
+
function toCurrentPullRequestStatus(item, fallbackHeadRefName) {
|
|
3067
|
+
if (!item.url || !item.title) {
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
3070
|
+
const repoIdentity = parseGitHubPullRequestRepo(item.url);
|
|
3071
|
+
const mergedAt = typeof item.mergedAt === "string" && item.mergedAt.trim().length > 0 ? item.mergedAt : null;
|
|
3072
|
+
let state;
|
|
3073
|
+
if (mergedAt !== null) {
|
|
3074
|
+
state = "merged";
|
|
3075
|
+
}
|
|
3076
|
+
else if (item.state.trim().length > 0) {
|
|
3077
|
+
state = item.state.toLowerCase();
|
|
3078
|
+
}
|
|
3079
|
+
else {
|
|
3080
|
+
state = "";
|
|
3081
|
+
}
|
|
3082
|
+
const checks = parseStatusCheckRollup(item.statusCheckRollup);
|
|
3083
|
+
return {
|
|
3084
|
+
...(typeof item.number === "number" ? { number: item.number } : {}),
|
|
3085
|
+
...(repoIdentity ? { repoOwner: repoIdentity.owner, repoName: repoIdentity.name } : {}),
|
|
3086
|
+
url: item.url,
|
|
3087
|
+
title: item.title,
|
|
3088
|
+
state,
|
|
3089
|
+
baseRefName: item.baseRefName,
|
|
3090
|
+
headRefName: item.headRefName || fallbackHeadRefName,
|
|
3091
|
+
isMerged: mergedAt !== null,
|
|
3092
|
+
isDraft: item.isDraft ?? false,
|
|
3093
|
+
mergeable: item.mergeable,
|
|
3094
|
+
checks,
|
|
3095
|
+
checksStatus: computeChecksStatus(checks),
|
|
3096
|
+
reviewDecision: mapReviewDecision(item.reviewDecision),
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
async function resolveGitHubEnterpriseHost(cwd) {
|
|
3100
|
+
let stdout;
|
|
3101
|
+
try {
|
|
3102
|
+
({ stdout } = await runGitCommand(["config", "--get", "remote.origin.url"], {
|
|
3103
|
+
cwd,
|
|
3104
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
3105
|
+
timeout: 5000,
|
|
3106
|
+
}));
|
|
3107
|
+
}
|
|
3108
|
+
catch {
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
const location = parseGitRemoteLocation(stdout.trim());
|
|
3112
|
+
if (!location) {
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
let host = location.host;
|
|
3116
|
+
if (!isGitHubHost(host) && (location.transport === "scp" || location.transport === "ssh")) {
|
|
3117
|
+
// An SSH alias (Host github-work → HostName ghe.acme.com) must be resolved
|
|
3118
|
+
// before probing/routing, or gh would be pointed at the alias name.
|
|
3119
|
+
host = (await resolveSshHostname(host)) ?? host;
|
|
3120
|
+
}
|
|
3121
|
+
// github.com (including via ssh alias) needs no GH_HOST — gh defaults there.
|
|
3122
|
+
// Only a self-hosted/Enterprise host must be passed explicitly.
|
|
3123
|
+
if (isGitHubHost(host)) {
|
|
3124
|
+
return null;
|
|
3125
|
+
}
|
|
3126
|
+
if (await probeGitHubAuthStatusForRouting(host)) {
|
|
3127
|
+
return host;
|
|
3128
|
+
}
|
|
3129
|
+
// A non-cloud (Enterprise) host that isn't authenticated must NOT resolve to
|
|
3130
|
+
// "no host": that would let gh default to github.com and silently query the
|
|
3131
|
+
// wrong server for a GHES workspace. Surface the auth gap instead, so read
|
|
3132
|
+
// paths (and authState) report unauthenticated rather than hitting github.com.
|
|
3133
|
+
throw new GitHubAuthenticationError({
|
|
3134
|
+
stderr: `GitHub CLI is not authenticated for host ${host}`,
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
function parseGitHubPullRequestRepo(url) {
|
|
3138
|
+
try {
|
|
3139
|
+
const parsed = new URL(url);
|
|
3140
|
+
// Host-agnostic on purpose: a GitHub Enterprise PR URL carries the instance
|
|
3141
|
+
// host, and the owner/name still live in the same `/owner/name/pull/N` path.
|
|
3142
|
+
const [owner, name, kind] = parsed.pathname.split("/").filter(Boolean);
|
|
3143
|
+
if (!owner || !name || kind !== "pull") {
|
|
3144
|
+
return null;
|
|
3145
|
+
}
|
|
3146
|
+
return { owner, name };
|
|
3147
|
+
}
|
|
3148
|
+
catch {
|
|
3149
|
+
return null;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
export function parseStatusCheckRollup(value, nowMs = Date.now()) {
|
|
3153
|
+
const directContexts = PullRequestStatusCheckRollupArraySchema.safeParse(value);
|
|
3154
|
+
if (!directContexts.success) {
|
|
3155
|
+
const legacyContexts = LegacyPullRequestStatusCheckRollupSchema.safeParse(value);
|
|
3156
|
+
if (!legacyContexts.success) {
|
|
3157
|
+
return [];
|
|
3158
|
+
}
|
|
3159
|
+
return parseStatusCheckRollup(legacyContexts.data.contexts, nowMs);
|
|
3160
|
+
}
|
|
3161
|
+
const dedupedChecks = new Map();
|
|
3162
|
+
for (const entry of directContexts.data) {
|
|
3163
|
+
const parsed = PullRequestStatusCheckRollupNodeSchema.safeParse(entry);
|
|
3164
|
+
if (!parsed.success) {
|
|
3165
|
+
continue;
|
|
3166
|
+
}
|
|
3167
|
+
const check = buildPullRequestCheck(parsed.data, nowMs);
|
|
3168
|
+
if (!check) {
|
|
3169
|
+
continue;
|
|
3170
|
+
}
|
|
3171
|
+
const existing = dedupedChecks.get(check.name);
|
|
3172
|
+
if (!existing || check.recency > existing.recency) {
|
|
3173
|
+
dedupedChecks.set(check.name, check);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
return Array.from(dedupedChecks.values(), ({ recency: _recency, ...check }) => check);
|
|
3177
|
+
}
|
|
3178
|
+
function buildPullRequestCheck(context, nowMs) {
|
|
3179
|
+
if (context.__typename === "CheckRun") {
|
|
3180
|
+
return {
|
|
3181
|
+
name: context.name,
|
|
3182
|
+
status: mapCheckRunStatus(context.status, context.conclusion),
|
|
3183
|
+
url: typeof context.detailsUrl === "string" ? context.detailsUrl : null,
|
|
3184
|
+
...(typeof context.workflowName === "string" && context.workflowName.trim().length > 0
|
|
3185
|
+
? { workflow: context.workflowName }
|
|
3186
|
+
: {}),
|
|
3187
|
+
...(typeof context.databaseId === "number" ? { checkRunId: context.databaseId } : {}),
|
|
3188
|
+
...(typeof context.checkSuite?.workflowRun?.databaseId === "number"
|
|
3189
|
+
? { workflowRunId: context.checkSuite.workflowRun.databaseId }
|
|
3190
|
+
: {}),
|
|
3191
|
+
...formatCheckRunDuration(context, nowMs),
|
|
3192
|
+
recency: getCheckRunRecency(context),
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
if (context.__typename === "StatusContext") {
|
|
3196
|
+
return {
|
|
3197
|
+
name: context.context,
|
|
3198
|
+
status: mapStatusContextState(context.state),
|
|
3199
|
+
url: typeof context.targetUrl === "string" ? context.targetUrl : null,
|
|
3200
|
+
recency: getStatusContextRecency(context),
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
return null;
|
|
3204
|
+
}
|
|
3205
|
+
function mapCheckRunStatus(status, conclusion) {
|
|
3206
|
+
if (status !== "COMPLETED") {
|
|
3207
|
+
return "pending";
|
|
3208
|
+
}
|
|
3209
|
+
switch (conclusion) {
|
|
3210
|
+
case "SUCCESS":
|
|
3211
|
+
return "success";
|
|
3212
|
+
case "FAILURE":
|
|
3213
|
+
case "TIMED_OUT":
|
|
3214
|
+
case "ACTION_REQUIRED":
|
|
3215
|
+
return "failure";
|
|
3216
|
+
case "CANCELLED":
|
|
3217
|
+
return "cancelled";
|
|
3218
|
+
case "SKIPPED":
|
|
3219
|
+
case "NEUTRAL":
|
|
3220
|
+
return "skipped";
|
|
3221
|
+
default:
|
|
3222
|
+
return "pending";
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
function mapStatusContextState(state) {
|
|
3226
|
+
switch (state) {
|
|
3227
|
+
case "SUCCESS":
|
|
3228
|
+
return "success";
|
|
3229
|
+
case "FAILURE":
|
|
3230
|
+
case "ERROR":
|
|
3231
|
+
return "failure";
|
|
3232
|
+
case "EXPECTED":
|
|
3233
|
+
case "PENDING":
|
|
3234
|
+
return "pending";
|
|
3235
|
+
default:
|
|
3236
|
+
return "pending";
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
function getCheckRunRecency(context) {
|
|
3240
|
+
const workflowRunId = context.checkSuite?.workflowRun?.databaseId;
|
|
3241
|
+
if (typeof workflowRunId === "number") {
|
|
3242
|
+
return workflowRunId;
|
|
3243
|
+
}
|
|
3244
|
+
return parseOptionalTime(context.completedAt ?? context.startedAt ?? null);
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* How long the check ran for. A finished run measures to its completion; a run still
|
|
3248
|
+
* going measures to now, so a client can say how long it has been waiting instead of
|
|
3249
|
+
* showing nothing. Raw timestamps never reach the client, so this is where the choice
|
|
3250
|
+
* between the two has to be made.
|
|
3251
|
+
*/
|
|
3252
|
+
function formatCheckRunDuration(context, nowMs) {
|
|
3253
|
+
const startedAt = parseOptionalTime(context.startedAt ?? null);
|
|
3254
|
+
if (startedAt <= 0) {
|
|
3255
|
+
return {};
|
|
3256
|
+
}
|
|
3257
|
+
const completedAt = parseOptionalTime(context.completedAt ?? null);
|
|
3258
|
+
const endedAt = completedAt > 0 ? completedAt : nowMs;
|
|
3259
|
+
if (endedAt < startedAt) {
|
|
3260
|
+
return {};
|
|
3261
|
+
}
|
|
3262
|
+
const durationSeconds = Math.floor((endedAt - startedAt) / 1000);
|
|
3263
|
+
return { duration: formatDurationSeconds(durationSeconds) };
|
|
3264
|
+
}
|
|
3265
|
+
function formatDurationSeconds(totalSeconds) {
|
|
3266
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
3267
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
3268
|
+
const seconds = totalSeconds % 60;
|
|
3269
|
+
const parts = [];
|
|
3270
|
+
if (hours > 0) {
|
|
3271
|
+
parts.push(`${hours}h`);
|
|
3272
|
+
}
|
|
3273
|
+
if (minutes > 0) {
|
|
3274
|
+
parts.push(`${minutes}m`);
|
|
3275
|
+
}
|
|
3276
|
+
if (seconds > 0 || parts.length === 0) {
|
|
3277
|
+
parts.push(`${seconds}s`);
|
|
3278
|
+
}
|
|
3279
|
+
return parts.join(" ");
|
|
3280
|
+
}
|
|
3281
|
+
function getStatusContextRecency(context) {
|
|
3282
|
+
return parseOptionalTime(context.createdAt ?? null);
|
|
3283
|
+
}
|
|
3284
|
+
function mapReviewDecision(value) {
|
|
3285
|
+
const reviewDecision = PullRequestReviewDecisionSchema.parse(value);
|
|
3286
|
+
if (reviewDecision === "APPROVED") {
|
|
3287
|
+
return "approved";
|
|
3288
|
+
}
|
|
3289
|
+
if (reviewDecision === "CHANGES_REQUESTED") {
|
|
3290
|
+
return "changes_requested";
|
|
3291
|
+
}
|
|
3292
|
+
if (reviewDecision === "REVIEW_REQUIRED") {
|
|
3293
|
+
return "pending";
|
|
3294
|
+
}
|
|
3295
|
+
return null;
|
|
3296
|
+
}
|
|
3297
|
+
//# sourceMappingURL=github-service.js.map
|