@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,230 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { isProcessAlive } from "#cli/auto-update/install-lock.js";
|
|
5
|
+
import { writeFileAtomicStrict } from "#shared-utils/write-json-file.js";
|
|
6
|
+
const LOCK_TIMEOUT_MS = 5_000;
|
|
7
|
+
const LOCK_POLL_MS = 25;
|
|
8
|
+
const LOCK_STALE_MS = 30_000;
|
|
9
|
+
/**
|
|
10
|
+
* Hard-age backstop: past this, a stale lock is reclaimed regardless of owner
|
|
11
|
+
* liveness. Prevents a recycled pid — or a `kill(pid,0)` that fails safe to
|
|
12
|
+
* "alive" (EPERM / unknown / cross-PID-namespace) — from wedging the lock
|
|
13
|
+
* forever. Mirrors `isInstallLockActive`, which likewise pairs liveness with an
|
|
14
|
+
* age bound rather than trusting liveness alone. Also serves as the reaper's own
|
|
15
|
+
* stale bound. Not a new deadline: it replaces the prior reaper-stale constant.
|
|
16
|
+
*/
|
|
17
|
+
const LOCK_STALE_HARD_MS = 5 * 60_000;
|
|
18
|
+
const LOCK_OWNER_FILE = "owner";
|
|
19
|
+
function sleepSync(ms) {
|
|
20
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
21
|
+
}
|
|
22
|
+
function isErrno(error, code) {
|
|
23
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
24
|
+
}
|
|
25
|
+
/** Directory age in ms, or `undefined` when the directory is gone (ENOENT). */
|
|
26
|
+
function directoryAgeMs(path, now) {
|
|
27
|
+
try {
|
|
28
|
+
return now - statSync(path).mtime.getTime();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (isErrno(error, "ENOENT"))
|
|
32
|
+
return undefined;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the lock's owner record. The token is the full `"<pid>:<uuid>"` string
|
|
38
|
+
* (identical to what release compares); `pid` is its leading integer. Returns
|
|
39
|
+
* `undefined` for a missing/empty/unparseable owner — i.e. a crash that never
|
|
40
|
+
* finished writing the record, which is reclaimable once age-stale.
|
|
41
|
+
*/
|
|
42
|
+
function readLockOwner(lockPath) {
|
|
43
|
+
let token;
|
|
44
|
+
try {
|
|
45
|
+
token = readFileSync(`${lockPath}/${LOCK_OWNER_FILE}`, "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const separator = token.indexOf(":");
|
|
51
|
+
if (separator <= 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
const pid = Number(token.slice(0, separator));
|
|
54
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
return { pid, token };
|
|
57
|
+
}
|
|
58
|
+
function tryAcquireDirectory(path) {
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(path);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (isErrno(error, "EEXIST"))
|
|
65
|
+
return false;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Serialize stale reclaim across contenders. A crashed reaper holder is itself
|
|
71
|
+
* reclaimed race-free: exactly one waiter's atomic rename of the reaper carcass
|
|
72
|
+
* succeeds; losers observe ENOENT and retry the mkdir. This is what stops two
|
|
73
|
+
* reclaimers from deleting each other's fresh lock — the original lost-update.
|
|
74
|
+
*
|
|
75
|
+
* Release is owner-token-checked, exactly like the main lock: if a paused reaper
|
|
76
|
+
* holder is stolen and replaced while it sleeps, its release sees a mismatched
|
|
77
|
+
* owner token and no-ops, so it cannot delete the successor's reaper and let a
|
|
78
|
+
* third reclaimer in behind it.
|
|
79
|
+
*/
|
|
80
|
+
function acquireReaperLock(path, deadline, now) {
|
|
81
|
+
const ownerPath = `${path}/${LOCK_OWNER_FILE}`;
|
|
82
|
+
const ownerToken = `${process.pid}:${randomUUID()}`;
|
|
83
|
+
while (!tryAcquireDirectory(path)) {
|
|
84
|
+
const ageMs = directoryAgeMs(path, now());
|
|
85
|
+
if (ageMs !== undefined && ageMs >= LOCK_STALE_HARD_MS) {
|
|
86
|
+
const carcass = `${path}.stale-${process.pid}-${randomUUID()}`;
|
|
87
|
+
try {
|
|
88
|
+
renameSync(path, carcass);
|
|
89
|
+
rmSync(carcass, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// A concurrent waiter won the atomic rename; retry the mkdir.
|
|
93
|
+
if (!isErrno(error, "ENOENT"))
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (now() >= deadline) {
|
|
99
|
+
throw new Error(`Timed out waiting for stale host config lock reaper: ${path}`);
|
|
100
|
+
}
|
|
101
|
+
sleepSync(LOCK_POLL_MS);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(ownerPath, ownerToken, "utf8");
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// Mirror the main lock: don't leave a reaper others must wait out on an
|
|
108
|
+
// owner-write failure.
|
|
109
|
+
rmSync(path, { recursive: true, force: true });
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
return () => {
|
|
113
|
+
try {
|
|
114
|
+
if (readFileSync(ownerPath, "utf8") !== ownerToken)
|
|
115
|
+
return;
|
|
116
|
+
rmSync(path, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (!isErrno(error, "ENOENT"))
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Remove a stale lock while holding the reaper, so at most one reclaimer acts at
|
|
126
|
+
* a time. Re-checks under the reaper: only a lock that is still age-stale AND
|
|
127
|
+
* whose owner is absent, dead, or past the hard-age bound is deleted. A live
|
|
128
|
+
* owner within the hard-age window is never reclaimed, so a slow-but-alive
|
|
129
|
+
* writer cannot lose its lock to a successor.
|
|
130
|
+
*/
|
|
131
|
+
function reclaimStaleLock(lockPath, reaperPath, deadline, now, isAlive, seams) {
|
|
132
|
+
const releaseReaper = acquireReaperLock(reaperPath, deadline, now);
|
|
133
|
+
try {
|
|
134
|
+
const ageMs = directoryAgeMs(lockPath, now());
|
|
135
|
+
if (ageMs === undefined || ageMs < LOCK_STALE_MS)
|
|
136
|
+
return;
|
|
137
|
+
const owner = readLockOwner(lockPath);
|
|
138
|
+
// Reclaim an absent owner record (a crash before the owner write) or a dead
|
|
139
|
+
// owner at the soft-stale age — recovering an abandoned lock fast is the whole
|
|
140
|
+
// point. A live owner waits for the hard-age backstop. The residual: a writer
|
|
141
|
+
// OS-suspended for 30s inside the two-statement mkdir/owner-write gap could be
|
|
142
|
+
// reclaimed and overlap; that window is a strict subset of the documented
|
|
143
|
+
// advisory-lock boundary (a suspended holder past the reclaim age), so it is
|
|
144
|
+
// accepted, not silently ignored.
|
|
145
|
+
const reclaimable = owner === undefined || !isAlive(owner.pid) || ageMs >= LOCK_STALE_HARD_MS;
|
|
146
|
+
if (!reclaimable)
|
|
147
|
+
return;
|
|
148
|
+
seams?.onBeforeReclaimDelete?.();
|
|
149
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
releaseReaper();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function acquireScaffolderFileLock(path, seams) {
|
|
156
|
+
const lockPath = `${path}.lock`;
|
|
157
|
+
const reaperPath = `${lockPath}.reaper`;
|
|
158
|
+
const ownerToken = `${process.pid}:${randomUUID()}`;
|
|
159
|
+
const ownerPath = `${lockPath}/${LOCK_OWNER_FILE}`;
|
|
160
|
+
const now = seams?.now ?? Date.now;
|
|
161
|
+
const isAlive = seams?.isAlive ?? isProcessAlive;
|
|
162
|
+
const deadline = now() + LOCK_TIMEOUT_MS;
|
|
163
|
+
for (;;) {
|
|
164
|
+
if (tryAcquireDirectory(lockPath)) {
|
|
165
|
+
try {
|
|
166
|
+
writeFileSync(ownerPath, ownerToken, "utf8");
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
return () => {
|
|
173
|
+
try {
|
|
174
|
+
if (readFileSync(ownerPath, "utf8") !== ownerToken)
|
|
175
|
+
return;
|
|
176
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (!isErrno(error, "ENOENT"))
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const ageMs = directoryAgeMs(lockPath, now());
|
|
185
|
+
if (ageMs !== undefined && ageMs >= LOCK_STALE_MS) {
|
|
186
|
+
// Reclaim may be a no-op (owner still alive within the hard-age window);
|
|
187
|
+
// fall through to the deadline check so a live holder can't spin us
|
|
188
|
+
// forever, then retry the mkdir on the next iteration.
|
|
189
|
+
reclaimStaleLock(lockPath, reaperPath, deadline, now, isAlive, seams);
|
|
190
|
+
}
|
|
191
|
+
if (now() >= deadline) {
|
|
192
|
+
throw new Error(`Timed out waiting for host config lock: ${path}`);
|
|
193
|
+
}
|
|
194
|
+
sleepSync(LOCK_POLL_MS);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Serialize a complete read/modify/write transaction for a host config file.
|
|
199
|
+
* These are local single-machine home-config locks (`~/.codex`, `~/.grok`), so
|
|
200
|
+
* owner-pid liveness is meaningful; this is not a shared-filesystem lock.
|
|
201
|
+
*/
|
|
202
|
+
export function withScaffolderFileLock(path, action, seams) {
|
|
203
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
204
|
+
const release = acquireScaffolderFileLock(path, seams);
|
|
205
|
+
try {
|
|
206
|
+
return action();
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
release();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Serialize a text-file read/modify/write and install the replacement strictly
|
|
214
|
+
* atomically without weakening an existing file's permissions.
|
|
215
|
+
*/
|
|
216
|
+
export function updateScaffolderTextFile(path, transform) {
|
|
217
|
+
return withScaffolderFileLock(path, () => {
|
|
218
|
+
const existed = existsSync(path);
|
|
219
|
+
if (existed && lstatSync(path).isSymbolicLink()) {
|
|
220
|
+
throw new Error(`Refusing to replace symlinked host config: ${path}`);
|
|
221
|
+
}
|
|
222
|
+
const existing = existed ? readFileSync(path, "utf8") : "";
|
|
223
|
+
const next = transform(existing);
|
|
224
|
+
if (next === existing)
|
|
225
|
+
return "unchanged";
|
|
226
|
+
const mode = existed ? lstatSync(path).mode & 0o777 : 0o600;
|
|
227
|
+
writeFileAtomicStrict(path, next, { encoding: "utf8", mode });
|
|
228
|
+
return "written";
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MergeOptions } from "#cli/commands/init/merge";
|
|
2
|
+
import { type WebpressoInstallProbe } from "#cli/commands/init/mcp-spec.js";
|
|
3
|
+
export declare const GROK_CONFIG_RELATIVE_PATH = ".grok/config.toml";
|
|
4
|
+
/** Render the managed `[mcp_servers.webpresso]` TOML block for Grok. */
|
|
5
|
+
export declare function grokWebpressoMcpBlock(entryPath: string): string;
|
|
6
|
+
export declare function upsertGrokWebpressoMcp(raw: string, entryPath: string): string;
|
|
7
|
+
export interface EnsureGrokWebpressoMcpInput {
|
|
8
|
+
readonly options: MergeOptions;
|
|
9
|
+
/** Project root whose `.grok/config.toml` is managed. */
|
|
10
|
+
readonly repoRoot: string;
|
|
11
|
+
/** Test seam — override the resolved MCP entry path. */
|
|
12
|
+
readonly entryPath?: string;
|
|
13
|
+
/** Test seam — override `<repoRoot>/.grok/config.toml`. */
|
|
14
|
+
readonly configPath?: string;
|
|
15
|
+
/** Test seam — override the install-discovery probe. */
|
|
16
|
+
readonly probe?: WebpressoInstallProbe;
|
|
17
|
+
}
|
|
18
|
+
export type EnsureGrokWebpressoMcpResult = {
|
|
19
|
+
kind: "grok-webpresso-mcp-written";
|
|
20
|
+
path: string;
|
|
21
|
+
entryPath: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "grok-webpresso-mcp-unchanged";
|
|
24
|
+
path: string;
|
|
25
|
+
entryPath: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "grok-webpresso-mcp-ambiguous";
|
|
28
|
+
path: string;
|
|
29
|
+
entryPath: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "grok-webpresso-mcp-skipped-dry-run";
|
|
32
|
+
path: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "grok-webpresso-mcp-not-installed";
|
|
35
|
+
path: string;
|
|
36
|
+
checked: readonly string[];
|
|
37
|
+
};
|
|
38
|
+
export declare function ensureGrokWebpressoMcp(input: EnsureGrokWebpressoMcpInput): EnsureGrokWebpressoMcpResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `grok-mcp` scaffolder preset.
|
|
3
|
+
*
|
|
4
|
+
* Grok Build reads persistent MCP servers from a project-local
|
|
5
|
+
* `.grok/config.toml`. A project entry *replaces the user entry whole, by
|
|
6
|
+
* name*, so the managed `[mcp_servers.webpresso]` block owns the webpresso
|
|
7
|
+
* alias for the repo. The schema mirrors Codex TOML (command/args/env inline
|
|
8
|
+
* table); we reuse Codex's deterministic per-server upsert so unrelated
|
|
9
|
+
* `[mcp_servers.*]`, `[plugins]`, and `[permission]` sections are preserved.
|
|
10
|
+
*
|
|
11
|
+
* The launcher is resolved to an absolute `bin/wp` path at scaffold time via
|
|
12
|
+
* the shared {@link resolveWebpressoMcpEntry}; startup only spawns a verified
|
|
13
|
+
* binary. Env is projected from the one canonical {@link WEBPRESSO_MCP_SPEC}.
|
|
14
|
+
*/
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { updateScaffolderTextFile } from "#cli/commands/init/scaffolders/file-transaction.js";
|
|
17
|
+
import { agentKitMcpLaunchCommand, resolveWebpressoMcpEntry, WEBPRESSO_MCP_SPEC, } from "#cli/commands/init/mcp-spec.js";
|
|
18
|
+
import { commandMcpHeader, hasAmbiguousMcpServerDottedDefinition, hasManagedMcpServerTableConflict, upsertMcpServerBlock, } from "#cli/commands/init/scaffolders/codex-mcp/index.js";
|
|
19
|
+
export const GROK_CONFIG_RELATIVE_PATH = ".grok/config.toml";
|
|
20
|
+
function tomlString(value) {
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
function tomlStringArray(values) {
|
|
24
|
+
return `[${values.map(tomlString).join(", ")}]`;
|
|
25
|
+
}
|
|
26
|
+
function tomlEnvInlineTable(env) {
|
|
27
|
+
const pairs = Object.entries(env).map(([key, value]) => `${key} = ${tomlString(value)}`);
|
|
28
|
+
return pairs.length > 0 ? `{ ${pairs.join(", ")} }` : "{}";
|
|
29
|
+
}
|
|
30
|
+
/** Render the managed `[mcp_servers.webpresso]` TOML block for Grok. */
|
|
31
|
+
export function grokWebpressoMcpBlock(entryPath) {
|
|
32
|
+
const launch = agentKitMcpLaunchCommand(entryPath);
|
|
33
|
+
const lines = [
|
|
34
|
+
commandMcpHeader(WEBPRESSO_MCP_SPEC.name),
|
|
35
|
+
`command = ${tomlString(launch.command)}`,
|
|
36
|
+
`args = ${tomlStringArray(launch.args)}`,
|
|
37
|
+
`env = ${tomlEnvInlineTable(WEBPRESSO_MCP_SPEC.env)}`,
|
|
38
|
+
"enabled = true",
|
|
39
|
+
];
|
|
40
|
+
return `${lines.join("\n")}\n`;
|
|
41
|
+
}
|
|
42
|
+
export function upsertGrokWebpressoMcp(raw, entryPath) {
|
|
43
|
+
return upsertMcpServerBlock(raw, WEBPRESSO_MCP_SPEC.name, grokWebpressoMcpBlock(entryPath));
|
|
44
|
+
}
|
|
45
|
+
export function ensureGrokWebpressoMcp(input) {
|
|
46
|
+
const configPath = input.configPath ?? join(input.repoRoot, ".grok", "config.toml");
|
|
47
|
+
if (input.options.dryRun) {
|
|
48
|
+
return { kind: "grok-webpresso-mcp-skipped-dry-run", path: configPath };
|
|
49
|
+
}
|
|
50
|
+
const resolution = input.entryPath === undefined ? resolveWebpressoMcpEntry(input.probe) : undefined;
|
|
51
|
+
const entryPath = input.entryPath ?? resolution?.entryPath;
|
|
52
|
+
if (!entryPath) {
|
|
53
|
+
const checked = resolution?.checked ?? [];
|
|
54
|
+
return { kind: "grok-webpresso-mcp-not-installed", path: configPath, checked };
|
|
55
|
+
}
|
|
56
|
+
let ambiguous = false;
|
|
57
|
+
const outcome = updateScaffolderTextFile(configPath, (existing) => {
|
|
58
|
+
if (hasAmbiguousMcpServerDottedDefinition(existing, WEBPRESSO_MCP_SPEC.name) ||
|
|
59
|
+
hasManagedMcpServerTableConflict(existing, WEBPRESSO_MCP_SPEC.name)) {
|
|
60
|
+
ambiguous = true;
|
|
61
|
+
return existing;
|
|
62
|
+
}
|
|
63
|
+
return upsertGrokWebpressoMcp(existing, entryPath);
|
|
64
|
+
});
|
|
65
|
+
if (ambiguous) {
|
|
66
|
+
return { kind: "grok-webpresso-mcp-ambiguous", path: configPath, entryPath };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
kind: outcome === "written" ? "grok-webpresso-mcp-written" : "grok-webpresso-mcp-unchanged",
|
|
70
|
+
path: configPath,
|
|
71
|
+
entryPath,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import type { MergeOptions } from "#cli/commands/init/merge";
|
|
2
|
+
import { type WebpressoInstallProbe } from "#cli/commands/init/mcp-spec.js";
|
|
2
3
|
export declare const STALE_OPENCODE_HOOK_PLUGIN_RELATIVE_PATH: string;
|
|
3
4
|
export declare const OPENCODE_PLUGIN_SUPPORT_LEVEL = "native-npm";
|
|
4
5
|
export declare const OPENCODE_PLUGIN_PACKAGE = "@webpresso/opencode-plugin";
|
|
6
|
+
export declare const OPENCODE_MCP_SERVER_NAME: "webpresso";
|
|
7
|
+
/**
|
|
8
|
+
* The canonical OpenCode `mcp.webpresso` entry. OpenCode's local-server shape
|
|
9
|
+
* folds the launcher and its args into a single `command` array, keys env
|
|
10
|
+
* under `environment`, and gates activation with `enabled`. Projected from the
|
|
11
|
+
* one canonical {@link WEBPRESSO_MCP_SPEC}.
|
|
12
|
+
*/
|
|
13
|
+
export declare function opencodeWebpressoMcpEntry(entryPath: string): {
|
|
14
|
+
type: "local";
|
|
15
|
+
command: string[];
|
|
16
|
+
enabled: true;
|
|
17
|
+
environment: Record<string, string>;
|
|
18
|
+
};
|
|
5
19
|
export interface EnsureOpenCodeWebpressoPluginInput {
|
|
6
20
|
readonly repoRoot: string;
|
|
7
21
|
readonly options: MergeOptions;
|
|
8
22
|
readonly configPath?: string;
|
|
23
|
+
/** Test seam — override the resolved MCP entry path. */
|
|
24
|
+
readonly entryPath?: string;
|
|
25
|
+
/** Test seam — override the install-discovery probe. */
|
|
26
|
+
readonly probe?: WebpressoInstallProbe;
|
|
9
27
|
}
|
|
10
28
|
export type EnsureOpenCodeWebpressoPluginResult = {
|
|
11
29
|
kind: "opencode-webpresso-plugin-written";
|
|
@@ -13,12 +31,15 @@ export type EnsureOpenCodeWebpressoPluginResult = {
|
|
|
13
31
|
pluginPackage: string;
|
|
14
32
|
removed: readonly string[];
|
|
15
33
|
preserved: readonly string[];
|
|
34
|
+
/** Absolute wp launcher wired into `mcp.webpresso`, or null when unresolved. */
|
|
35
|
+
mcpEntryPath: string | null;
|
|
16
36
|
} | {
|
|
17
37
|
kind: "opencode-webpresso-plugin-unchanged";
|
|
18
38
|
path: string;
|
|
19
39
|
pluginPackage: string;
|
|
20
40
|
removed: readonly string[];
|
|
21
41
|
preserved: readonly string[];
|
|
42
|
+
mcpEntryPath: string | null;
|
|
22
43
|
} | {
|
|
23
44
|
kind: "opencode-webpresso-plugin-skipped-dry-run";
|
|
24
45
|
path: string;
|