@webpresso/agent-kit 3.1.30 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +183 -20
- package/catalog/AGENTS.md.tpl +3 -3
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +28 -1
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/verify/SKILL.md +4 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
- package/dist/esm/audit/blueprint-trust.js +14 -10
- package/dist/esm/audit/changeset-evidence.d.ts +23 -0
- package/dist/esm/audit/changeset-evidence.js +30 -0
- package/dist/esm/audit/changeset-required.d.ts +10 -0
- package/dist/esm/audit/changeset-required.js +73 -0
- package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
- package/dist/esm/audit/ci-guardrails-detection.js +68 -0
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +13 -1
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +101 -2
- package/dist/esm/blueprint/core/schema.d.ts +1 -0
- package/dist/esm/blueprint/core/schema.js +26 -0
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +87 -20
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/sync/client.d.ts +2 -1
- package/dist/esm/blueprint/sync/client.js +1 -1
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
- package/dist/esm/blueprint/trust/dossier.js +40 -6
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +74 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +15 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +43 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
- package/dist/esm/cli/commands/agent-launch.js +93 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +21 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +13 -1
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +30 -6
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.js +2 -2
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +3 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
- package/dist/esm/cli/commands/init/package-root.js +10 -2
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
- package/dist/esm/cli/commands/opencode-probe.js +323 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
- package/dist/esm/cli/commands/opencode-rotate.js +147 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/quality-runner.js +3 -3
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +751 -209
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/rust-check.d.ts +24 -0
- package/dist/esm/cli/commands/rust-check.js +110 -0
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/typecheck.d.ts +1 -0
- package/dist/esm/cli/commands/typecheck.js +30 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
- package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
- package/dist/esm/cli/commands/worktree/router.js +10 -0
- package/dist/esm/cli/direct-provider-launch.js +9 -5
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +5 -3
- package/dist/esm/hooks/doctor.js +67 -16
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +50 -9
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +5 -1
- package/dist/esm/mcp/tools/audit.js +42 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/package.json +2 -0
- package/dist/esm/platform/client.d.ts +7 -1
- package/dist/esm/platform/github-contract-source.d.ts +2 -1
- package/dist/esm/pr-description/contract.js +24 -4
- package/dist/esm/review/authority.js +27 -14
- package/dist/esm/review/availability.d.ts +33 -0
- package/dist/esm/review/availability.js +122 -20
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.d.ts +11 -1
- package/dist/esm/review/execution/adapters.js +36 -10
- package/dist/esm/review/execution/artifacts.d.ts +1 -1
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +121 -21
- package/dist/esm/review/execution/supervisor.js +36 -4
- package/dist/esm/review/execution/types.d.ts +9 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +25 -0
- package/dist/esm/review/subject.js +116 -26
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/rust/affected-manifests.d.ts +38 -0
- package/dist/esm/rust/affected-manifests.js +69 -0
- package/dist/esm/session-memory/fetch-index.d.ts +1 -1
- package/dist/esm/status/snapshot.js +32 -25
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -2
- package/dist/esm/test-helpers/global-setup.js +79 -32
- package/dist/esm/typecheck/planner.d.ts +9 -1
- package/dist/esm/typecheck/planner.js +31 -0
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
- package/dist/esm/worktrees/orphan-scan.js +211 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -13
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createSummaryOutputSchema, createSummaryResult } from "#mcp/tools/_shared/result.js";
|
|
3
|
+
import { redactText } from "#mcp/tools/_shared/redact.js";
|
|
4
|
+
import { isMissingBinary, isRunFailure, runCommand } from "#mcp/tools/_shared/run-command.js";
|
|
5
|
+
import { readonlyOpsBaseSchema, resolveReadonlyCwd, runReadonlyCommand, } from "#mcp/tools/_readonly-ops.js";
|
|
6
|
+
export const RUN_WAIT_UNTIL_VALUES = ["run_terminal", "all_jobs_terminal"];
|
|
7
|
+
const SELECTOR_REQUIRED_MESSAGE = "wp run wait requires a selector: pass --sha <head-sha> or --run-id <id> (no implicit latest-run guessing)";
|
|
8
|
+
const SELECTOR_CONFLICT_MESSAGE = "wp run wait accepts exactly one selector: pass either --sha or --run-id, not both";
|
|
9
|
+
const runIdSchema = z.union([
|
|
10
|
+
z.number().int().positive(),
|
|
11
|
+
z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(/^[1-9][0-9]*$/u, "runId must be a positive integer (no signs, decimals, or suffixes)"),
|
|
14
|
+
]);
|
|
15
|
+
const inputSchema = readonlyOpsBaseSchema
|
|
16
|
+
.extend({
|
|
17
|
+
// Workflow-run JSON (jobs + steps) is much larger than PR metadata, so the
|
|
18
|
+
// effective default budget is the shared schema's ceiling rather than 4 KB.
|
|
19
|
+
maxOutputBytes: z.number().int().positive().max(64_000).optional().default(64_000),
|
|
20
|
+
sha: z.string().min(1).optional(),
|
|
21
|
+
runId: runIdSchema.optional(),
|
|
22
|
+
workflow: z.string().min(1).optional(),
|
|
23
|
+
until: z.enum(RUN_WAIT_UNTIL_VALUES).optional().default("run_terminal"),
|
|
24
|
+
pollIntervalMs: z.number().int().positive().max(300_000).optional().default(15_000),
|
|
25
|
+
})
|
|
26
|
+
.strict()
|
|
27
|
+
.superRefine((value, ctx) => {
|
|
28
|
+
const hasSha = value.sha !== undefined;
|
|
29
|
+
const hasRunId = value.runId !== undefined;
|
|
30
|
+
if (!hasSha && !hasRunId) {
|
|
31
|
+
ctx.addIssue({
|
|
32
|
+
code: z.ZodIssueCode.custom,
|
|
33
|
+
message: SELECTOR_REQUIRED_MESSAGE,
|
|
34
|
+
path: ["sha"],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (hasSha && hasRunId) {
|
|
38
|
+
ctx.addIssue({
|
|
39
|
+
code: z.ZodIssueCode.custom,
|
|
40
|
+
message: SELECTOR_CONFLICT_MESSAGE,
|
|
41
|
+
path: ["runId"],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const runWaitJobSchema = z.object({
|
|
46
|
+
name: z.string(),
|
|
47
|
+
status: z.string(),
|
|
48
|
+
conclusion: z.string().nullable(),
|
|
49
|
+
failedStep: z.string().optional(),
|
|
50
|
+
errorTail: z.string().optional(),
|
|
51
|
+
upstreamCulprits: z.array(z.string()).optional(),
|
|
52
|
+
classification: z.literal("blocked_upstream").optional(),
|
|
53
|
+
transient: z.string().optional(),
|
|
54
|
+
});
|
|
55
|
+
const outputSchema = createSummaryOutputSchema({
|
|
56
|
+
counts: z.record(z.string(), z.number()),
|
|
57
|
+
details: z.object({
|
|
58
|
+
cwd: z.string(),
|
|
59
|
+
sha: z.string().optional(),
|
|
60
|
+
runId: z.number().optional(),
|
|
61
|
+
workflow: z.string().optional(),
|
|
62
|
+
until: z.enum(RUN_WAIT_UNTIL_VALUES),
|
|
63
|
+
timeoutMs: z.number(),
|
|
64
|
+
pollIntervalMs: z.number(),
|
|
65
|
+
pollCount: z.number(),
|
|
66
|
+
elapsedMs: z.number(),
|
|
67
|
+
commands: z.array(z.record(z.string(), z.unknown())),
|
|
68
|
+
}),
|
|
69
|
+
}).extend({
|
|
70
|
+
status: z.enum(["success", "failed", "timed_out", "no_run"]),
|
|
71
|
+
verdict: z.enum(["passed", "failed", "pending"]),
|
|
72
|
+
terminalReason: z.enum([
|
|
73
|
+
"run_terminal",
|
|
74
|
+
"all_jobs_terminal",
|
|
75
|
+
"run_failed",
|
|
76
|
+
"timeout",
|
|
77
|
+
"no_run",
|
|
78
|
+
"command_failed",
|
|
79
|
+
]),
|
|
80
|
+
conclusion: z.string().nullable(),
|
|
81
|
+
runId: z.number().nullable(),
|
|
82
|
+
runUrl: z.string().optional(),
|
|
83
|
+
workflowName: z.string().optional(),
|
|
84
|
+
headSha: z.string().optional(),
|
|
85
|
+
until: z.enum(RUN_WAIT_UNTIL_VALUES),
|
|
86
|
+
jobs: z.array(runWaitJobSchema),
|
|
87
|
+
});
|
|
88
|
+
const RUN_VIEW_FIELDS = "databaseId,status,conclusion,workflowName,headSha,url,name,event,jobs";
|
|
89
|
+
const RUN_LIST_FIELDS = "databaseId,headSha,workflowName,status,conclusion,createdAt,url,name,event";
|
|
90
|
+
const PROBE_TIMEOUT_MS = 30_000;
|
|
91
|
+
const MAX_ERROR_TAIL_LINES = 50;
|
|
92
|
+
const MAX_ERROR_TAIL_BYTES = 2_000;
|
|
93
|
+
const MAX_FAILURE_SNIPPET_CHARS = 200;
|
|
94
|
+
/**
|
|
95
|
+
* Terminal job conclusions that warrant failure diagnostics (failed step,
|
|
96
|
+
* error tail, transient classification, upstream-culprit attribution).
|
|
97
|
+
* `skipped` and `neutral` are terminal but not unsuccessful.
|
|
98
|
+
*/
|
|
99
|
+
const UNSUCCESSFUL_JOB_CONCLUSIONS = new Set([
|
|
100
|
+
"failure",
|
|
101
|
+
"timed_out",
|
|
102
|
+
"cancelled",
|
|
103
|
+
"action_required",
|
|
104
|
+
]);
|
|
105
|
+
// Transient classifier v1 — regex on (failed-step name + errorTail).
|
|
106
|
+
const TRANSIENT_CLASSIFIERS = [
|
|
107
|
+
{
|
|
108
|
+
id: "pulumi-stack-lock",
|
|
109
|
+
pattern: /\[409\] Conflict: Another update is currently in progress/u,
|
|
110
|
+
},
|
|
111
|
+
{ id: "node-gyp-cache-perms", pattern: /gyp .*Permission denied/u },
|
|
112
|
+
{
|
|
113
|
+
id: "playwright-browser-missing",
|
|
114
|
+
pattern: /Executable doesn't exist.*playwright|playwright.*Executable doesn't exist/iu,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
export function classifyTransient(text) {
|
|
118
|
+
for (const classifier of TRANSIENT_CLASSIFIERS) {
|
|
119
|
+
if (classifier.pattern.test(text))
|
|
120
|
+
return classifier.id;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/** Distinguish provider-level failures so they never collapse into a generic command_failed. */
|
|
125
|
+
function classifyGhFailure(id, output) {
|
|
126
|
+
if (!output)
|
|
127
|
+
return undefined;
|
|
128
|
+
if (/rate limit|HTTP 429|secondary rate/iu.test(output)) {
|
|
129
|
+
return `github-rate-limited: ${id} hit a GitHub API rate limit`;
|
|
130
|
+
}
|
|
131
|
+
if (/HTTP 401|Bad credentials|authentication|gh auth login/iu.test(output)) {
|
|
132
|
+
return `github-auth-failed: ${id} could not authenticate with GitHub`;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
function skippedResult(id, args) {
|
|
137
|
+
return {
|
|
138
|
+
id,
|
|
139
|
+
command: { command: "gh", args },
|
|
140
|
+
passed: false,
|
|
141
|
+
skipped: true,
|
|
142
|
+
warnings: [`${id} skipped: timeout budget exhausted`],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Deadline-aware gh probe through the shared bounded read-only path
|
|
147
|
+
* (`runReadonlyCommand`): output is budget-clipped and redacted, spawn
|
|
148
|
+
* diagnostics (stderr, timedOut, aborted) are preserved, and no probe starts
|
|
149
|
+
* once the overall deadline is spent. Per-probe timeout is the remaining
|
|
150
|
+
* budget capped at PROBE_TIMEOUT_MS — never a fixed floor past the deadline.
|
|
151
|
+
*/
|
|
152
|
+
async function runGhBounded(id, args, options) {
|
|
153
|
+
const remainingMs = options.deadline - Date.now();
|
|
154
|
+
if (remainingMs <= 0)
|
|
155
|
+
return skippedResult(id, args);
|
|
156
|
+
const result = await runReadonlyCommand(id, "gh", args, {
|
|
157
|
+
cwd: options.cwd,
|
|
158
|
+
timeoutMs: Math.min(remainingMs, PROBE_TIMEOUT_MS),
|
|
159
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
160
|
+
signal: options.signal,
|
|
161
|
+
parseJson: options.parseJson,
|
|
162
|
+
});
|
|
163
|
+
if (result.passed || result.missingBinary)
|
|
164
|
+
return result;
|
|
165
|
+
const warnings = [...(result.warnings ?? [])];
|
|
166
|
+
const classified = classifyGhFailure(id, result.rawOutput);
|
|
167
|
+
if (classified)
|
|
168
|
+
warnings.push(classified);
|
|
169
|
+
if (result.timedOut)
|
|
170
|
+
warnings.push(`${id} timed out before completing`);
|
|
171
|
+
if (result.aborted)
|
|
172
|
+
warnings.push(`${id} aborted by cancellation`);
|
|
173
|
+
if (warnings.length === 0) {
|
|
174
|
+
const snippet = (result.rawOutput ?? "").slice(0, MAX_FAILURE_SNIPPET_CHARS).trim();
|
|
175
|
+
warnings.push(`${id} failed (exit ${result.exitCode ?? "unknown"})${snippet ? `: ${snippet}` : ""}`);
|
|
176
|
+
}
|
|
177
|
+
return { ...result, warnings };
|
|
178
|
+
}
|
|
179
|
+
function toSlimCommand(result) {
|
|
180
|
+
return {
|
|
181
|
+
id: result.id,
|
|
182
|
+
command: result.command.command,
|
|
183
|
+
args: result.command.args,
|
|
184
|
+
passed: result.passed,
|
|
185
|
+
...(result.exitCode === undefined ? {} : { exitCode: result.exitCode }),
|
|
186
|
+
...(result.timedOut ? { timedOut: true } : {}),
|
|
187
|
+
...(result.aborted ? { aborted: true } : {}),
|
|
188
|
+
...(result.missingBinary ? { missingBinary: true } : {}),
|
|
189
|
+
...(result.skipped ? { skipped: true } : {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function asString(value) {
|
|
193
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
194
|
+
}
|
|
195
|
+
function asNumber(value) {
|
|
196
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
197
|
+
}
|
|
198
|
+
/** UTF-8-byte-bounded suffix; strips a split leading multibyte character. */
|
|
199
|
+
function tailBytes(text, maxBytes) {
|
|
200
|
+
const buffer = Buffer.from(text, "utf8");
|
|
201
|
+
if (buffer.byteLength <= maxBytes)
|
|
202
|
+
return text;
|
|
203
|
+
return buffer
|
|
204
|
+
.subarray(buffer.byteLength - maxBytes)
|
|
205
|
+
.toString("utf8")
|
|
206
|
+
.replace(/^�+/u, "");
|
|
207
|
+
}
|
|
208
|
+
function boundedLogTail(text) {
|
|
209
|
+
const lines = text.split(/\r?\n/u).filter((line) => line.length > 0);
|
|
210
|
+
const tail = lines.slice(-MAX_ERROR_TAIL_LINES).join("\n");
|
|
211
|
+
const redacted = redactText(tail) ?? "";
|
|
212
|
+
return tailBytes(redacted, MAX_ERROR_TAIL_BYTES);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Exact-match selection on top of the server-side `gh run list --commit
|
|
216
|
+
* <sha>` filter: entries missing headSha are NOT matches and there is no
|
|
217
|
+
* fallback pool. Because list output flows through the shared redactor
|
|
218
|
+
* (which masks 40+-char tokens, including full commit SHAs), equality also
|
|
219
|
+
* accepts the deterministic mask of the requested SHA — never any other
|
|
220
|
+
* value. Workflow filtering is delegated to `gh run list --workflow`, which
|
|
221
|
+
* accepts both file names and display names — a local name-equality
|
|
222
|
+
* re-filter would wrongly exclude file-name selectors.
|
|
223
|
+
*/
|
|
224
|
+
function selectNewestRunId(entries, sha) {
|
|
225
|
+
const accepted = new Set([sha, redactText(sha) ?? sha]);
|
|
226
|
+
const matching = entries.filter((entry) => {
|
|
227
|
+
const headSha = asString(entry.headSha);
|
|
228
|
+
return headSha !== undefined && accepted.has(headSha);
|
|
229
|
+
});
|
|
230
|
+
const newest = [...matching].sort((a, b) => (asString(b.createdAt) ?? "").localeCompare(asString(a.createdAt) ?? ""));
|
|
231
|
+
for (const entry of newest) {
|
|
232
|
+
const runId = asNumber(entry.databaseId);
|
|
233
|
+
if (runId !== undefined)
|
|
234
|
+
return runId;
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
function firstUnsuccessfulStep(job) {
|
|
239
|
+
if (!Array.isArray(job.steps))
|
|
240
|
+
return undefined;
|
|
241
|
+
for (const step of job.steps) {
|
|
242
|
+
const conclusion = asString(step.conclusion);
|
|
243
|
+
if (conclusion !== undefined && UNSUCCESSFUL_JOB_CONCLUSIONS.has(conclusion)) {
|
|
244
|
+
const name = asString(step.name);
|
|
245
|
+
return name === undefined ? undefined : redactText(name);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
function runIsTerminal(run) {
|
|
251
|
+
return asString(run.status) === "completed";
|
|
252
|
+
}
|
|
253
|
+
function jobsAllTerminal(jobs) {
|
|
254
|
+
return jobs.every((job) => asString(job.status) === "completed");
|
|
255
|
+
}
|
|
256
|
+
function isDone(until, run, jobs) {
|
|
257
|
+
if (!runIsTerminal(run))
|
|
258
|
+
return false;
|
|
259
|
+
if (until === "all_jobs_terminal")
|
|
260
|
+
return jobsAllTerminal(jobs);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
async function fetchErrorTail(job, _run, context) {
|
|
264
|
+
const jobId = asNumber(job.databaseId);
|
|
265
|
+
if (jobId === undefined)
|
|
266
|
+
return undefined;
|
|
267
|
+
// gh substitutes {owner}/{repo} from the checkout's repository (and host),
|
|
268
|
+
// so no URL parsing is needed — job/run URLs pass through the shared
|
|
269
|
+
// redactor and are not reliable to parse.
|
|
270
|
+
const args = ["api", `repos/{owner}/{repo}/actions/jobs/${jobId}/logs`];
|
|
271
|
+
const remainingMs = context.deadline - Date.now();
|
|
272
|
+
if (remainingMs <= 0) {
|
|
273
|
+
context.commands.push(skippedResult("run_wait_job_logs", args));
|
|
274
|
+
context.warnings.push(`error-tail enrichment skipped for job ${String(asString(job.name) ?? jobId)}: timeout budget exhausted`);
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
// Deviates from runReadonlyCommand on purpose: its budget clip keeps the
|
|
278
|
+
// HEAD of the output, but failure diagnostics live at the TAIL of a job
|
|
279
|
+
// log. Spawn diagnostics are still preserved in the command record and the
|
|
280
|
+
// returned tail is line- and UTF-8-byte-bounded plus redacted; raw log
|
|
281
|
+
// bytes never enter the result payload.
|
|
282
|
+
const outcome = await runCommand("gh", args, {
|
|
283
|
+
cwd: context.cwd,
|
|
284
|
+
timeoutMs: Math.min(remainingMs, PROBE_TIMEOUT_MS),
|
|
285
|
+
signal: context.signal,
|
|
286
|
+
});
|
|
287
|
+
if (isRunFailure(outcome)) {
|
|
288
|
+
context.commands.push({
|
|
289
|
+
id: "run_wait_job_logs",
|
|
290
|
+
command: { command: "gh", args },
|
|
291
|
+
passed: false,
|
|
292
|
+
missingBinary: isMissingBinary(outcome),
|
|
293
|
+
warnings: [
|
|
294
|
+
isMissingBinary(outcome)
|
|
295
|
+
? "missing binary: gh"
|
|
296
|
+
: `failed to spawn gh: ${outcome.error.message}`,
|
|
297
|
+
],
|
|
298
|
+
});
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const failureWarning = outcome.exitCode === 0
|
|
302
|
+
? undefined
|
|
303
|
+
: (classifyGhFailure("run_wait_job_logs", outcome.stderr) ??
|
|
304
|
+
`run_wait_job_logs failed (exit ${outcome.exitCode})`);
|
|
305
|
+
context.commands.push({
|
|
306
|
+
id: "run_wait_job_logs",
|
|
307
|
+
command: { command: "gh", args },
|
|
308
|
+
passed: outcome.exitCode === 0,
|
|
309
|
+
exitCode: outcome.exitCode,
|
|
310
|
+
timedOut: outcome.timedOut || undefined,
|
|
311
|
+
aborted: outcome.aborted || undefined,
|
|
312
|
+
...(failureWarning ? { warnings: [failureWarning] } : {}),
|
|
313
|
+
});
|
|
314
|
+
if (outcome.exitCode !== 0)
|
|
315
|
+
return undefined;
|
|
316
|
+
const tail = boundedLogTail(outcome.stdout);
|
|
317
|
+
return tail.length > 0 ? tail : undefined;
|
|
318
|
+
}
|
|
319
|
+
async function normalizeJobs(rawJobs, run, context, options) {
|
|
320
|
+
const unsuccessfulNames = rawJobs
|
|
321
|
+
.filter((job) => {
|
|
322
|
+
const conclusion = asString(job.conclusion);
|
|
323
|
+
return conclusion !== undefined && UNSUCCESSFUL_JOB_CONCLUSIONS.has(conclusion);
|
|
324
|
+
})
|
|
325
|
+
.map((job) => asString(job.name) ?? "")
|
|
326
|
+
.filter((name) => name.length > 0);
|
|
327
|
+
const jobs = [];
|
|
328
|
+
for (const raw of rawJobs) {
|
|
329
|
+
const name = asString(raw.name) ?? "";
|
|
330
|
+
const status = (asString(raw.status) ?? "");
|
|
331
|
+
const conclusion = (asString(raw.conclusion) ?? null);
|
|
332
|
+
const job = { name, status, conclusion };
|
|
333
|
+
if (conclusion !== null && UNSUCCESSFUL_JOB_CONCLUSIONS.has(conclusion)) {
|
|
334
|
+
const failedStep = firstUnsuccessfulStep(raw);
|
|
335
|
+
if (failedStep)
|
|
336
|
+
job.failedStep = failedStep;
|
|
337
|
+
const errorTail = options.fetchLogs ? await fetchErrorTail(raw, run, context) : undefined;
|
|
338
|
+
if (errorTail)
|
|
339
|
+
job.errorTail = errorTail;
|
|
340
|
+
const transient = classifyTransient(`${failedStep ?? ""}\n${errorTail ?? ""}`);
|
|
341
|
+
if (transient)
|
|
342
|
+
job.transient = transient;
|
|
343
|
+
}
|
|
344
|
+
if (conclusion === "skipped" && unsuccessfulNames.length > 0) {
|
|
345
|
+
job.classification = "blocked_upstream";
|
|
346
|
+
job.upstreamCulprits = unsuccessfulNames;
|
|
347
|
+
}
|
|
348
|
+
jobs.push(job);
|
|
349
|
+
}
|
|
350
|
+
return jobs;
|
|
351
|
+
}
|
|
352
|
+
function counts(pollCount, jobs) {
|
|
353
|
+
return {
|
|
354
|
+
pollCount,
|
|
355
|
+
jobCount: jobs.length,
|
|
356
|
+
failedJobCount: jobs.filter((job) => job.conclusion !== null && UNSUCCESSFUL_JOB_CONCLUSIONS.has(job.conclusion)).length,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function extractRawJobs(run, warnings) {
|
|
360
|
+
const rawValue = Array.isArray(run.jobs) ? run.jobs : [];
|
|
361
|
+
const jobs = rawValue.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.name === "string");
|
|
362
|
+
if (jobs.length !== rawValue.length) {
|
|
363
|
+
warnings.push("some job entries were omitted by the output-budget sanitizer");
|
|
364
|
+
}
|
|
365
|
+
return jobs;
|
|
366
|
+
}
|
|
367
|
+
async function sleep(ms, signal) {
|
|
368
|
+
if (ms <= 0)
|
|
369
|
+
return;
|
|
370
|
+
await new Promise((resolve) => {
|
|
371
|
+
let settled = false;
|
|
372
|
+
const finish = () => {
|
|
373
|
+
if (settled)
|
|
374
|
+
return;
|
|
375
|
+
settled = true;
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
signal?.removeEventListener("abort", onAbort);
|
|
378
|
+
resolve();
|
|
379
|
+
};
|
|
380
|
+
const timer = setTimeout(finish, ms);
|
|
381
|
+
const onAbort = () => finish();
|
|
382
|
+
if (signal) {
|
|
383
|
+
if (signal.aborted)
|
|
384
|
+
finish();
|
|
385
|
+
else
|
|
386
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
export async function runRunWait(raw, extra) {
|
|
391
|
+
const input = inputSchema.parse(raw ?? {});
|
|
392
|
+
const cwd = resolveReadonlyCwd(input);
|
|
393
|
+
const startedAt = Date.now();
|
|
394
|
+
const deadline = startedAt + input.timeoutMs;
|
|
395
|
+
const signal = extra?.signal;
|
|
396
|
+
const commands = [];
|
|
397
|
+
const warnings = [];
|
|
398
|
+
let pollCount = 0;
|
|
399
|
+
const finish = (outcome, jobs, run) => {
|
|
400
|
+
const allWarnings = [...commands.flatMap((command) => command.warnings ?? []), ...warnings];
|
|
401
|
+
const workflowName = asString(run?.workflowName);
|
|
402
|
+
const runUrl = asString(run?.url);
|
|
403
|
+
const headSha = asString(run?.headSha);
|
|
404
|
+
return {
|
|
405
|
+
...outcome,
|
|
406
|
+
...(runUrl ? { runUrl } : {}),
|
|
407
|
+
...(workflowName ? { workflowName } : {}),
|
|
408
|
+
...(headSha ? { headSha } : {}),
|
|
409
|
+
until: input.until,
|
|
410
|
+
jobs,
|
|
411
|
+
counts: counts(pollCount, jobs),
|
|
412
|
+
details: {
|
|
413
|
+
cwd,
|
|
414
|
+
sha: input.sha,
|
|
415
|
+
runId: typeof input.runId === "number" ? input.runId : undefined,
|
|
416
|
+
workflow: input.workflow,
|
|
417
|
+
until: input.until,
|
|
418
|
+
timeoutMs: input.timeoutMs,
|
|
419
|
+
pollIntervalMs: input.pollIntervalMs,
|
|
420
|
+
pollCount,
|
|
421
|
+
elapsedMs: Date.now() - startedAt,
|
|
422
|
+
commands: commands.map(toSlimCommand),
|
|
423
|
+
},
|
|
424
|
+
...(allWarnings.length > 0 ? { warnings: allWarnings } : {}),
|
|
425
|
+
};
|
|
426
|
+
};
|
|
427
|
+
const commandFailed = (summary) => finish({
|
|
428
|
+
status: "failed",
|
|
429
|
+
passed: false,
|
|
430
|
+
verdict: "failed",
|
|
431
|
+
summary,
|
|
432
|
+
terminalReason: "command_failed",
|
|
433
|
+
conclusion: null,
|
|
434
|
+
runId: null,
|
|
435
|
+
}, []);
|
|
436
|
+
// Resolve the selector to a run id ONCE, then watch only that run: a rerun
|
|
437
|
+
// appearing mid-watch must never silently replace the run being watched.
|
|
438
|
+
let boundRunId;
|
|
439
|
+
if (input.runId !== undefined) {
|
|
440
|
+
boundRunId = typeof input.runId === "number" ? input.runId : Number.parseInt(input.runId, 10);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
const sha = input.sha;
|
|
444
|
+
const listArgs = [
|
|
445
|
+
"run",
|
|
446
|
+
"list",
|
|
447
|
+
"--commit",
|
|
448
|
+
sha,
|
|
449
|
+
...(input.workflow ? ["--workflow", input.workflow] : []),
|
|
450
|
+
"--limit",
|
|
451
|
+
"20",
|
|
452
|
+
"--json",
|
|
453
|
+
RUN_LIST_FIELDS,
|
|
454
|
+
];
|
|
455
|
+
const list = await runGhBounded("run_wait_list", listArgs, {
|
|
456
|
+
cwd,
|
|
457
|
+
deadline,
|
|
458
|
+
maxOutputBytes: input.maxOutputBytes,
|
|
459
|
+
signal,
|
|
460
|
+
});
|
|
461
|
+
commands.push(list);
|
|
462
|
+
if (list.missingBinary)
|
|
463
|
+
return commandFailed("run wait failed: missing binary gh");
|
|
464
|
+
if (!list.passed)
|
|
465
|
+
return commandFailed("run wait failed while listing workflow runs");
|
|
466
|
+
if (list.truncated) {
|
|
467
|
+
warnings.push("run list output exceeded the output budget; raise maxOutputBytes or use --run-id");
|
|
468
|
+
return commandFailed("run wait failed: run list output exceeded the output budget");
|
|
469
|
+
}
|
|
470
|
+
let parsed;
|
|
471
|
+
try {
|
|
472
|
+
parsed = JSON.parse(list.rawOutput?.trim() || "[]");
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return commandFailed("run wait failed: run list returned invalid JSON");
|
|
476
|
+
}
|
|
477
|
+
const resolved = Array.isArray(parsed)
|
|
478
|
+
? selectNewestRunId(parsed, sha)
|
|
479
|
+
: undefined;
|
|
480
|
+
if (resolved === undefined) {
|
|
481
|
+
return finish({
|
|
482
|
+
status: "no_run",
|
|
483
|
+
passed: false,
|
|
484
|
+
verdict: "failed",
|
|
485
|
+
summary: `run wait found no workflow run with head sha ${sha}${input.workflow ? ` for workflow ${input.workflow}` : ""}`,
|
|
486
|
+
terminalReason: "no_run",
|
|
487
|
+
conclusion: null,
|
|
488
|
+
runId: null,
|
|
489
|
+
}, []);
|
|
490
|
+
}
|
|
491
|
+
boundRunId = resolved;
|
|
492
|
+
}
|
|
493
|
+
const viewArgs = ["run", "view", String(boundRunId), "--json", RUN_VIEW_FIELDS];
|
|
494
|
+
let lastRun;
|
|
495
|
+
let lastJobs = [];
|
|
496
|
+
while (!signal?.aborted && Date.now() < deadline) {
|
|
497
|
+
pollCount += 1;
|
|
498
|
+
const view = await runGhBounded("run_wait_view", viewArgs, {
|
|
499
|
+
cwd,
|
|
500
|
+
deadline,
|
|
501
|
+
maxOutputBytes: input.maxOutputBytes,
|
|
502
|
+
signal,
|
|
503
|
+
parseJson: true,
|
|
504
|
+
});
|
|
505
|
+
commands.push(view);
|
|
506
|
+
if (view.skipped)
|
|
507
|
+
break;
|
|
508
|
+
if (view.missingBinary)
|
|
509
|
+
return commandFailed("run wait failed: missing binary gh");
|
|
510
|
+
if (!view.passed)
|
|
511
|
+
return commandFailed("run wait failed while reading workflow run state");
|
|
512
|
+
const run = (view.details ?? {});
|
|
513
|
+
const jobs = extractRawJobs(run, warnings);
|
|
514
|
+
lastRun = run;
|
|
515
|
+
lastJobs = jobs;
|
|
516
|
+
if (isDone(input.until, run, jobs)) {
|
|
517
|
+
const context = {
|
|
518
|
+
cwd,
|
|
519
|
+
deadline,
|
|
520
|
+
maxOutputBytes: input.maxOutputBytes,
|
|
521
|
+
signal,
|
|
522
|
+
commands,
|
|
523
|
+
warnings,
|
|
524
|
+
};
|
|
525
|
+
const enriched = await normalizeJobs(jobs, run, context, { fetchLogs: true });
|
|
526
|
+
const conclusion = (asString(run.conclusion) ?? null);
|
|
527
|
+
const green = conclusion === "success";
|
|
528
|
+
const terminalReason = green
|
|
529
|
+
? input.until === "all_jobs_terminal"
|
|
530
|
+
? "all_jobs_terminal"
|
|
531
|
+
: "run_terminal"
|
|
532
|
+
: "run_failed";
|
|
533
|
+
const workflowName = asString(run.workflowName);
|
|
534
|
+
return finish({
|
|
535
|
+
status: green ? "success" : "failed",
|
|
536
|
+
passed: green,
|
|
537
|
+
verdict: green ? "passed" : "failed",
|
|
538
|
+
summary: green
|
|
539
|
+
? `run wait reached ${terminalReason} for run ${boundRunId}${workflowName ? ` (${workflowName})` : ""} with conclusion success`
|
|
540
|
+
: `run wait: run ${boundRunId}${workflowName ? ` (${workflowName})` : ""} finished with conclusion ${conclusion ?? "unknown"}`,
|
|
541
|
+
terminalReason,
|
|
542
|
+
conclusion,
|
|
543
|
+
runId: boundRunId,
|
|
544
|
+
}, enriched, run);
|
|
545
|
+
}
|
|
546
|
+
const sleepMs = Math.min(input.pollIntervalMs, deadline - Date.now());
|
|
547
|
+
if (sleepMs <= 0)
|
|
548
|
+
break;
|
|
549
|
+
await sleep(sleepMs, signal);
|
|
550
|
+
}
|
|
551
|
+
// Timeout / cancellation: report the last-observed job states (normalized,
|
|
552
|
+
// no log downloads) instead of discarding known partial progress.
|
|
553
|
+
const timeoutContext = {
|
|
554
|
+
cwd,
|
|
555
|
+
deadline,
|
|
556
|
+
maxOutputBytes: input.maxOutputBytes,
|
|
557
|
+
signal,
|
|
558
|
+
commands,
|
|
559
|
+
warnings,
|
|
560
|
+
};
|
|
561
|
+
const partialJobs = lastRun
|
|
562
|
+
? await normalizeJobs(lastJobs, lastRun, timeoutContext, { fetchLogs: false })
|
|
563
|
+
: [];
|
|
564
|
+
return finish({
|
|
565
|
+
status: "timed_out",
|
|
566
|
+
passed: false,
|
|
567
|
+
verdict: "pending",
|
|
568
|
+
summary: `run wait timed out after ${input.timeoutMs}ms waiting for ${input.until}`,
|
|
569
|
+
terminalReason: "timeout",
|
|
570
|
+
conclusion: (asString(lastRun?.conclusion) ?? null),
|
|
571
|
+
runId: boundRunId,
|
|
572
|
+
}, partialJobs, lastRun);
|
|
573
|
+
}
|
|
574
|
+
const tool = {
|
|
575
|
+
name: "wp_run_wait",
|
|
576
|
+
description: "Read-only GitHub workflow-run waiter via the gh CLI: blocks until a push/dispatch run (e.g. a production deploy on main) reaches run-terminal or all-jobs-terminal state, then reports conclusion, failed-step tails, transient classification, and blocked-upstream culprits. Selector is required and exclusive (--sha newest exact-match run, or --run-id direct); timeoutMs (<=300000) is an overall deadline covering probes and log enrichment. Does not mutate run state.",
|
|
577
|
+
inputSchema,
|
|
578
|
+
outputSchema,
|
|
579
|
+
annotations: {
|
|
580
|
+
title: "Workflow run wait",
|
|
581
|
+
readOnlyHint: true,
|
|
582
|
+
destructiveHint: false,
|
|
583
|
+
idempotentHint: true,
|
|
584
|
+
openWorldHint: false,
|
|
585
|
+
},
|
|
586
|
+
handler: async (raw, extra) => {
|
|
587
|
+
const payload = await runRunWait(raw, extra);
|
|
588
|
+
return createSummaryResult(payload, {
|
|
589
|
+
isError: payload.status !== "success",
|
|
590
|
+
});
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
export default tool;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolDescriptor, ToolHandlerExtra, ToolHandlerResult } from "#mcp/auto-discover";
|
|
2
2
|
export interface SessionFetchAndIndexDeps {
|
|
3
|
-
readonly fetchImpl?:
|
|
3
|
+
readonly fetchImpl?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
4
4
|
}
|
|
5
5
|
export declare function handleSessionFetchAndIndex(raw: unknown, extra?: ToolHandlerExtra, deps?: SessionFetchAndIndexDeps): Promise<ToolHandlerResult>;
|
|
6
6
|
declare const tool: ToolDescriptor;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readCurrentSessionId } from "#session-memory/current-session.js";
|
|
3
|
+
import { createSummaryOutputSchema, createSummaryResult } from "./_shared/result.js";
|
|
4
|
+
const inputSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
cwd: z.string().optional(),
|
|
7
|
+
})
|
|
8
|
+
.strict();
|
|
9
|
+
const outputSchema = createSummaryOutputSchema({
|
|
10
|
+
details: z.object({
|
|
11
|
+
sessionId: z.string(),
|
|
12
|
+
}),
|
|
13
|
+
}).extend({
|
|
14
|
+
sessionId: z.string(),
|
|
15
|
+
});
|
|
16
|
+
function effectiveCwd(cwd) {
|
|
17
|
+
return cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
18
|
+
}
|
|
19
|
+
const tool = {
|
|
20
|
+
name: "wp_session_id",
|
|
21
|
+
description: "Report only the current agent session id (empty if unknown), skipping the cross-worktree execution-model scan `wp_session_info` performs. Cheaper for hot paths — this is the lookup `wp_pr_upsert` uses internally to auto-resolve an omitted `description.sessionId`. Prefer `wp_session_info` when the observed execution model(s) are also needed.",
|
|
22
|
+
inputSchema,
|
|
23
|
+
outputSchema,
|
|
24
|
+
annotations: {
|
|
25
|
+
title: "Session id",
|
|
26
|
+
readOnlyHint: true,
|
|
27
|
+
destructiveHint: false,
|
|
28
|
+
idempotentHint: true,
|
|
29
|
+
openWorldHint: false,
|
|
30
|
+
},
|
|
31
|
+
handler: async (raw) => {
|
|
32
|
+
const input = inputSchema.parse(raw ?? {});
|
|
33
|
+
const sessionId = readCurrentSessionId(effectiveCwd(input.cwd), process.env);
|
|
34
|
+
const payload = {
|
|
35
|
+
passed: true,
|
|
36
|
+
summary: sessionId
|
|
37
|
+
? `session id ${sessionId}`
|
|
38
|
+
: "no live or captured session id found; use Session-skip in the PR disclosure",
|
|
39
|
+
sessionId,
|
|
40
|
+
details: { sessionId },
|
|
41
|
+
};
|
|
42
|
+
return createSummaryResult(payload);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export default tool;
|