@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.
- 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
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { withScaffolderFileLock } from "#cli/commands/init/scaffolders/file-transaction.js";
|
|
4
|
+
import { writeFileAtomicStrict } from "#shared-utils/write-json-file.js";
|
|
5
|
+
import { agentKitMcpLaunchCommand, resolveWebpressoMcpEntry, WEBPRESSO_MCP_SPEC, } from "#cli/commands/init/mcp-spec.js";
|
|
3
6
|
const STALE_OPENCODE_HOOK_PLUGIN_PATH_PARTS = [
|
|
4
7
|
".opencode",
|
|
5
8
|
"plugins",
|
|
@@ -9,6 +12,22 @@ export const STALE_OPENCODE_HOOK_PLUGIN_RELATIVE_PATH = STALE_OPENCODE_HOOK_PLUG
|
|
|
9
12
|
export const OPENCODE_PLUGIN_SUPPORT_LEVEL = "native-npm";
|
|
10
13
|
export const OPENCODE_PLUGIN_PACKAGE = "@webpresso/opencode-plugin";
|
|
11
14
|
const OPENCODE_PLUGIN_CONFIG_KEY = "plugin";
|
|
15
|
+
export const OPENCODE_MCP_SERVER_NAME = WEBPRESSO_MCP_SPEC.name;
|
|
16
|
+
/**
|
|
17
|
+
* The canonical OpenCode `mcp.webpresso` entry. OpenCode's local-server shape
|
|
18
|
+
* folds the launcher and its args into a single `command` array, keys env
|
|
19
|
+
* under `environment`, and gates activation with `enabled`. Projected from the
|
|
20
|
+
* one canonical {@link WEBPRESSO_MCP_SPEC}.
|
|
21
|
+
*/
|
|
22
|
+
export function opencodeWebpressoMcpEntry(entryPath) {
|
|
23
|
+
const launch = agentKitMcpLaunchCommand(entryPath);
|
|
24
|
+
return {
|
|
25
|
+
type: "local",
|
|
26
|
+
command: [launch.command, ...launch.args],
|
|
27
|
+
enabled: true,
|
|
28
|
+
environment: { ...WEBPRESSO_MCP_SPEC.env },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
12
31
|
const LEGACY_WEBPRESSO_HOOK_PLUGIN_MARKERS = [
|
|
13
32
|
"Generated by webpresso (`wp setup`).",
|
|
14
33
|
"export const WebpressoHooksPlugin",
|
|
@@ -20,53 +39,36 @@ function isJsonRecord(value) {
|
|
|
20
39
|
function isStringArray(value) {
|
|
21
40
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22
41
|
}
|
|
23
|
-
function commandParts(value) {
|
|
24
|
-
if (!isJsonRecord(value))
|
|
25
|
-
return [];
|
|
26
|
-
const command = value.command;
|
|
27
|
-
if (typeof command === "string")
|
|
28
|
-
return [command];
|
|
29
|
-
if (Array.isArray(command)) {
|
|
30
|
-
return command.filter((part) => typeof part === "string");
|
|
31
|
-
}
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
42
|
function isPotentialWebpressoOpenCodeMcpAlias(name, value) {
|
|
35
|
-
return (name === "agent-kit" || name === "webpresso"
|
|
43
|
+
return isJsonRecord(value) && (name === "agent-kit" || name === "webpresso");
|
|
36
44
|
}
|
|
37
|
-
function
|
|
38
|
-
const parts = commandParts(value);
|
|
39
|
-
if (parts.length === 0)
|
|
40
|
-
return false;
|
|
41
|
-
const commandText = parts.join(" ");
|
|
42
|
-
const isNamedLegacyAlias = name === "agent-kit" || name === "webpresso";
|
|
43
|
-
return (parts.some((part) => /[/\\]agent-kit[/\\]/u.test(part) ||
|
|
44
|
-
part.includes("@webpresso/agent-kit") ||
|
|
45
|
-
part.includes("src/mcp/cli.ts")) ||
|
|
46
|
-
(isNamedLegacyAlias && /(?:^|\s)(?:vp\s+exec\s+)?wp\s+mcp(?:\s|$)/u.test(commandText)));
|
|
47
|
-
}
|
|
48
|
-
function collectPreservedLegacyMcpAliases(value, removeOwned) {
|
|
45
|
+
function collectPreservedLegacyMcpAliases(value) {
|
|
49
46
|
if (!isJsonRecord(value))
|
|
50
47
|
return [];
|
|
51
48
|
return Object.entries(value)
|
|
52
49
|
.filter(([name, server]) => isPotentialWebpressoOpenCodeMcpAlias(name, server) &&
|
|
53
|
-
!(
|
|
50
|
+
!isKnownWebpressoOwnedLegacyMcpAlias(name, server))
|
|
54
51
|
.map(([name]) => `mcp:${name}`)
|
|
55
52
|
.toSorted();
|
|
56
53
|
}
|
|
57
|
-
function
|
|
54
|
+
function commandWords(value) {
|
|
58
55
|
if (!isJsonRecord(value))
|
|
59
|
-
return
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
return [];
|
|
57
|
+
const command = value.command;
|
|
58
|
+
return isStringArray(command) ? command : [];
|
|
59
|
+
}
|
|
60
|
+
function isKnownWebpressoOwnedLegacyMcpAlias(name, value) {
|
|
61
|
+
const command = commandWords(value);
|
|
62
|
+
const commandText = command.join(" ");
|
|
63
|
+
if (name === "webpresso") {
|
|
64
|
+
return command.join("\0") === ["vp", "exec", "wp", "mcp"].join("\0");
|
|
68
65
|
}
|
|
69
|
-
|
|
66
|
+
if (name !== "agent-kit")
|
|
67
|
+
return false;
|
|
68
|
+
return (commandText.includes("@webpresso/agent-kit") ||
|
|
69
|
+
commandText.includes(".claude/plugins/cache") ||
|
|
70
|
+
commandText.includes("/agent-kit/src/mcp/") ||
|
|
71
|
+
commandText.includes("\\agent-kit\\src\\mcp\\"));
|
|
70
72
|
}
|
|
71
73
|
function isWebpressoOwnedStaleHookPlugin(staleHookPluginPath) {
|
|
72
74
|
if (!existsSync(staleHookPluginPath))
|
|
@@ -74,15 +76,488 @@ function isWebpressoOwnedStaleHookPlugin(staleHookPluginPath) {
|
|
|
74
76
|
const staleHookPluginContent = readFileSync(staleHookPluginPath, "utf8");
|
|
75
77
|
return LEGACY_WEBPRESSO_HOOK_PLUGIN_MARKERS.every((marker) => staleHookPluginContent.includes(marker));
|
|
76
78
|
}
|
|
79
|
+
function stripJsoncForParse(raw) {
|
|
80
|
+
let out = "";
|
|
81
|
+
let inString = false;
|
|
82
|
+
let quote = null;
|
|
83
|
+
let escaped = false;
|
|
84
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
85
|
+
const ch = raw[i];
|
|
86
|
+
const next = raw[i + 1];
|
|
87
|
+
if (inString) {
|
|
88
|
+
out += ch;
|
|
89
|
+
if (escaped) {
|
|
90
|
+
escaped = false;
|
|
91
|
+
}
|
|
92
|
+
else if (ch === "\\") {
|
|
93
|
+
escaped = true;
|
|
94
|
+
}
|
|
95
|
+
else if (ch === quote) {
|
|
96
|
+
inString = false;
|
|
97
|
+
quote = null;
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (ch === '"' || ch === "'") {
|
|
102
|
+
inString = true;
|
|
103
|
+
quote = ch;
|
|
104
|
+
out += ch;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (ch === "/" && next === "/") {
|
|
108
|
+
while (i < raw.length && raw[i] !== "\n")
|
|
109
|
+
i += 1;
|
|
110
|
+
out += "\n";
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (ch === "/" && next === "*") {
|
|
114
|
+
i += 2;
|
|
115
|
+
while (i < raw.length && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
116
|
+
i += 1;
|
|
117
|
+
i += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
out += ch;
|
|
121
|
+
}
|
|
122
|
+
return stripJsoncTrailingCommas(out);
|
|
123
|
+
}
|
|
124
|
+
function stripJsoncTrailingCommas(raw) {
|
|
125
|
+
let out = "";
|
|
126
|
+
let inString = false;
|
|
127
|
+
let quote = null;
|
|
128
|
+
let escaped = false;
|
|
129
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
130
|
+
const ch = raw[i];
|
|
131
|
+
if (inString) {
|
|
132
|
+
out += ch;
|
|
133
|
+
if (escaped) {
|
|
134
|
+
escaped = false;
|
|
135
|
+
}
|
|
136
|
+
else if (ch === "\\") {
|
|
137
|
+
escaped = true;
|
|
138
|
+
}
|
|
139
|
+
else if (ch === quote) {
|
|
140
|
+
inString = false;
|
|
141
|
+
quote = null;
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (ch === '"' || ch === "'") {
|
|
146
|
+
inString = true;
|
|
147
|
+
quote = ch;
|
|
148
|
+
out += ch;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (ch === ",") {
|
|
152
|
+
let j = i + 1;
|
|
153
|
+
while (/\s/u.test(raw[j] ?? ""))
|
|
154
|
+
j += 1;
|
|
155
|
+
if (raw[j] === "}" || raw[j] === "]")
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
out += ch;
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
function parseOpenCodeConfig(existing, configPath) {
|
|
163
|
+
if (existing.trim().length === 0)
|
|
164
|
+
return {};
|
|
165
|
+
const parseInput = configPath.endsWith(".jsonc") ? stripJsoncForParse(existing) : existing;
|
|
166
|
+
return JSON.parse(parseInput);
|
|
167
|
+
}
|
|
168
|
+
function isJsoncConfigPath(configPath) {
|
|
169
|
+
return configPath.endsWith(".jsonc");
|
|
170
|
+
}
|
|
171
|
+
function findMatchingJsoncBracket(raw, openIndex) {
|
|
172
|
+
const open = raw[openIndex];
|
|
173
|
+
const close = open === "{" ? "}" : open === "[" ? "]" : null;
|
|
174
|
+
if (!close)
|
|
175
|
+
return -1;
|
|
176
|
+
let depth = 0;
|
|
177
|
+
let inString = false;
|
|
178
|
+
let quote = null;
|
|
179
|
+
let escaped = false;
|
|
180
|
+
for (let i = openIndex; i < raw.length; i += 1) {
|
|
181
|
+
const ch = raw[i];
|
|
182
|
+
const next = raw[i + 1];
|
|
183
|
+
if (inString) {
|
|
184
|
+
if (escaped)
|
|
185
|
+
escaped = false;
|
|
186
|
+
else if (ch === "\\")
|
|
187
|
+
escaped = true;
|
|
188
|
+
else if (ch === quote) {
|
|
189
|
+
inString = false;
|
|
190
|
+
quote = null;
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === '"' || ch === "'") {
|
|
195
|
+
inString = true;
|
|
196
|
+
quote = ch;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (ch === "/" && next === "/") {
|
|
200
|
+
while (i < raw.length && raw[i] !== "\n")
|
|
201
|
+
i += 1;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (ch === "/" && next === "*") {
|
|
205
|
+
i += 2;
|
|
206
|
+
while (i < raw.length && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
207
|
+
i += 1;
|
|
208
|
+
i += 1;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (ch === open)
|
|
212
|
+
depth += 1;
|
|
213
|
+
if (ch === close) {
|
|
214
|
+
depth -= 1;
|
|
215
|
+
if (depth === 0)
|
|
216
|
+
return i;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return -1;
|
|
220
|
+
}
|
|
221
|
+
function skipJsoncTrivia(raw, start) {
|
|
222
|
+
let i = start;
|
|
223
|
+
while (i < raw.length) {
|
|
224
|
+
const ch = raw[i];
|
|
225
|
+
const next = raw[i + 1];
|
|
226
|
+
if (/\s/u.test(ch)) {
|
|
227
|
+
i += 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (ch === "/" && next === "/") {
|
|
231
|
+
while (i < raw.length && raw[i] !== "\n")
|
|
232
|
+
i += 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (ch === "/" && next === "*") {
|
|
236
|
+
i += 2;
|
|
237
|
+
while (i < raw.length && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
238
|
+
i += 1;
|
|
239
|
+
i += 2;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
return i;
|
|
245
|
+
}
|
|
246
|
+
function parseJsoncStringToken(raw, start) {
|
|
247
|
+
if (raw[start] !== '"')
|
|
248
|
+
return null;
|
|
249
|
+
let json = '"';
|
|
250
|
+
for (let i = start + 1; i < raw.length; i += 1) {
|
|
251
|
+
const ch = raw[i];
|
|
252
|
+
json += ch;
|
|
253
|
+
if (ch === "\\") {
|
|
254
|
+
i += 1;
|
|
255
|
+
if (i < raw.length)
|
|
256
|
+
json += raw[i];
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (ch === '"') {
|
|
260
|
+
try {
|
|
261
|
+
return { value: JSON.parse(json), end: i + 1 };
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function findJsoncValueEnd(raw, start, objectEnd) {
|
|
271
|
+
const ch = raw[start];
|
|
272
|
+
if (ch === "{" || ch === "[") {
|
|
273
|
+
const end = findMatchingJsoncBracket(raw, start);
|
|
274
|
+
return end === -1 ? objectEnd : end + 1;
|
|
275
|
+
}
|
|
276
|
+
if (ch === '"')
|
|
277
|
+
return parseJsoncStringToken(raw, start)?.end ?? objectEnd;
|
|
278
|
+
let i = start;
|
|
279
|
+
while (i < objectEnd) {
|
|
280
|
+
const current = raw[i];
|
|
281
|
+
const next = raw[i + 1];
|
|
282
|
+
if (current === "/" && next === "/") {
|
|
283
|
+
i += 2;
|
|
284
|
+
while (i < objectEnd && raw[i] !== "\n")
|
|
285
|
+
i += 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (current === "/" && next === "*") {
|
|
289
|
+
i += 2;
|
|
290
|
+
while (i < objectEnd && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
291
|
+
i += 1;
|
|
292
|
+
i = Math.min(i + 2, objectEnd);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (current === "," || current === "}")
|
|
296
|
+
break;
|
|
297
|
+
i += 1;
|
|
298
|
+
}
|
|
299
|
+
return i;
|
|
300
|
+
}
|
|
301
|
+
function findJsoncObjectProperty(raw, objectStart, objectEnd, key) {
|
|
302
|
+
let i = objectStart + 1;
|
|
303
|
+
while (i < objectEnd) {
|
|
304
|
+
i = skipJsoncTrivia(raw, i);
|
|
305
|
+
if (i >= objectEnd || raw[i] === "}")
|
|
306
|
+
return null;
|
|
307
|
+
const keyToken = parseJsoncStringToken(raw, i);
|
|
308
|
+
if (!keyToken)
|
|
309
|
+
return null;
|
|
310
|
+
const colon = skipJsoncTrivia(raw, keyToken.end);
|
|
311
|
+
if (raw[colon] !== ":")
|
|
312
|
+
return null;
|
|
313
|
+
const valueStart = skipJsoncTrivia(raw, colon + 1);
|
|
314
|
+
const valueEnd = findJsoncValueEnd(raw, valueStart, objectEnd);
|
|
315
|
+
if (keyToken.value === key)
|
|
316
|
+
return { keyStart: i, valueStart, valueEnd };
|
|
317
|
+
i = skipJsoncTrivia(raw, valueEnd);
|
|
318
|
+
if (raw[i] === ",")
|
|
319
|
+
i += 1;
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
function countJsoncObjectProperties(raw, objectStart, objectEnd, key) {
|
|
324
|
+
let count = 0;
|
|
325
|
+
let i = objectStart + 1;
|
|
326
|
+
while (i < objectEnd) {
|
|
327
|
+
i = skipJsoncTrivia(raw, i);
|
|
328
|
+
if (i >= objectEnd || raw[i] === "}")
|
|
329
|
+
return count;
|
|
330
|
+
const keyToken = parseJsoncStringToken(raw, i);
|
|
331
|
+
if (!keyToken)
|
|
332
|
+
return count;
|
|
333
|
+
const colon = skipJsoncTrivia(raw, keyToken.end);
|
|
334
|
+
if (raw[colon] !== ":")
|
|
335
|
+
return count;
|
|
336
|
+
const valueStart = skipJsoncTrivia(raw, colon + 1);
|
|
337
|
+
const valueEnd = findJsoncValueEnd(raw, valueStart, objectEnd);
|
|
338
|
+
if (keyToken.value === key)
|
|
339
|
+
count += 1;
|
|
340
|
+
i = skipJsoncTrivia(raw, valueEnd);
|
|
341
|
+
if (raw[i] === ",")
|
|
342
|
+
i += 1;
|
|
343
|
+
}
|
|
344
|
+
return count;
|
|
345
|
+
}
|
|
346
|
+
function hasDuplicateManagedJsoncKeys(raw, configPath) {
|
|
347
|
+
if (!isJsoncConfigPath(configPath) || raw.trim().length === 0)
|
|
348
|
+
return false;
|
|
349
|
+
const rootStart = skipJsoncTrivia(raw, 0);
|
|
350
|
+
if (rootStart >= raw.length || raw[rootStart] !== "{")
|
|
351
|
+
return false;
|
|
352
|
+
const rootEnd = findMatchingJsoncBracket(raw, rootStart);
|
|
353
|
+
if (rootEnd === -1)
|
|
354
|
+
return false;
|
|
355
|
+
if (countJsoncObjectProperties(raw, rootStart, rootEnd, OPENCODE_PLUGIN_CONFIG_KEY) > 1)
|
|
356
|
+
return true;
|
|
357
|
+
if (countJsoncObjectProperties(raw, rootStart, rootEnd, "mcp") > 1)
|
|
358
|
+
return true;
|
|
359
|
+
const mcpProp = findJsoncObjectProperty(raw, rootStart, rootEnd, "mcp");
|
|
360
|
+
if (mcpProp === null)
|
|
361
|
+
return false;
|
|
362
|
+
const mcpEnd = findMatchingJsoncBracket(raw, mcpProp.valueStart);
|
|
363
|
+
if (mcpEnd === -1)
|
|
364
|
+
return false;
|
|
365
|
+
return countJsoncObjectProperties(raw, mcpProp.valueStart, mcpEnd, OPENCODE_MCP_SERVER_NAME) > 1;
|
|
366
|
+
}
|
|
367
|
+
function previousJsoncSignificantChar(raw, before) {
|
|
368
|
+
let last = "";
|
|
369
|
+
let inString = false;
|
|
370
|
+
let quote = null;
|
|
371
|
+
let escaped = false;
|
|
372
|
+
for (let i = 0; i < before; i += 1) {
|
|
373
|
+
const ch = raw[i];
|
|
374
|
+
const next = raw[i + 1];
|
|
375
|
+
if (inString) {
|
|
376
|
+
if (escaped)
|
|
377
|
+
escaped = false;
|
|
378
|
+
else if (ch === "\\")
|
|
379
|
+
escaped = true;
|
|
380
|
+
else if (ch === quote) {
|
|
381
|
+
inString = false;
|
|
382
|
+
quote = null;
|
|
383
|
+
}
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (ch === '"' || ch === "'") {
|
|
387
|
+
inString = true;
|
|
388
|
+
quote = ch;
|
|
389
|
+
last = ch;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (ch === "/" && next === "/") {
|
|
393
|
+
while (i < before && raw[i] !== "\n")
|
|
394
|
+
i += 1;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (ch === "/" && next === "*") {
|
|
398
|
+
i += 2;
|
|
399
|
+
while (i < before && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
400
|
+
i += 1;
|
|
401
|
+
i += 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (!/\s/u.test(ch))
|
|
405
|
+
last = ch;
|
|
406
|
+
}
|
|
407
|
+
return last;
|
|
408
|
+
}
|
|
409
|
+
function insertJsoncObjectProperty(raw, objectEnd, key, value, indent) {
|
|
410
|
+
const previous = previousJsoncSignificantChar(raw, objectEnd);
|
|
411
|
+
const needsComma = previous !== "{" && previous !== ",";
|
|
412
|
+
const prefix = needsComma ? "," : "";
|
|
413
|
+
const rendered = JSON.stringify(value, null, 2)
|
|
414
|
+
.split("\n")
|
|
415
|
+
.map((line, index) => (index === 0 ? line : `${indent}${line}`))
|
|
416
|
+
.join("\n");
|
|
417
|
+
return `${raw.slice(0, objectEnd)}${prefix}\n${indent}${JSON.stringify(key)}: ${rendered}\n${raw.slice(objectEnd)}`;
|
|
418
|
+
}
|
|
419
|
+
function removeJsoncObjectProperty(raw, property, objectStart) {
|
|
420
|
+
const afterValue = skipJsoncTrivia(raw, property.valueEnd);
|
|
421
|
+
if (raw[afterValue] === ",") {
|
|
422
|
+
return `${raw.slice(0, property.keyStart)}${raw.slice(afterValue + 1)}`;
|
|
423
|
+
}
|
|
424
|
+
let previousComma = -1;
|
|
425
|
+
let inString = false;
|
|
426
|
+
let quote = null;
|
|
427
|
+
let escaped = false;
|
|
428
|
+
for (let i = objectStart + 1; i < property.keyStart; i += 1) {
|
|
429
|
+
const ch = raw[i];
|
|
430
|
+
const next = raw[i + 1];
|
|
431
|
+
if (inString) {
|
|
432
|
+
if (escaped)
|
|
433
|
+
escaped = false;
|
|
434
|
+
else if (ch === "\\")
|
|
435
|
+
escaped = true;
|
|
436
|
+
else if (ch === quote) {
|
|
437
|
+
inString = false;
|
|
438
|
+
quote = null;
|
|
439
|
+
}
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (ch === '"' || ch === "'") {
|
|
443
|
+
inString = true;
|
|
444
|
+
quote = ch;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (ch === "/" && next === "/") {
|
|
448
|
+
while (i < property.keyStart && raw[i] !== "\n")
|
|
449
|
+
i += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (ch === "/" && next === "*") {
|
|
453
|
+
i += 2;
|
|
454
|
+
while (i < property.keyStart && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
455
|
+
i += 1;
|
|
456
|
+
i += 1;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (ch === ",")
|
|
460
|
+
previousComma = i;
|
|
461
|
+
}
|
|
462
|
+
if (previousComma !== -1) {
|
|
463
|
+
return `${raw.slice(0, previousComma)}${raw.slice(property.valueEnd)}`;
|
|
464
|
+
}
|
|
465
|
+
return `${raw.slice(0, property.keyStart)}${raw.slice(property.valueEnd)}`;
|
|
466
|
+
}
|
|
467
|
+
function appendJsoncArrayString(raw, arrayEnd, value, indent) {
|
|
468
|
+
const previous = previousJsoncSignificantChar(raw, arrayEnd);
|
|
469
|
+
const needsComma = previous !== "[" && previous !== ",";
|
|
470
|
+
const prefix = needsComma ? "," : "";
|
|
471
|
+
return `${raw.slice(0, arrayEnd)}${prefix}\n${indent}${JSON.stringify(value)}\n${raw.slice(arrayEnd)}`;
|
|
472
|
+
}
|
|
473
|
+
function renderOpenCodeConfig(existing, configPath, nextRoot, nextPlugins, hasPluginRegistration, mcpEntryPath, removedMcpServerNames) {
|
|
474
|
+
if (!isJsoncConfigPath(configPath))
|
|
475
|
+
return `${JSON.stringify(nextRoot, null, 2)}\n`;
|
|
476
|
+
if (existing.trim().length === 0)
|
|
477
|
+
return `${JSON.stringify(nextRoot, null, 2)}\n`;
|
|
478
|
+
let next = existing;
|
|
479
|
+
const rootStart = skipJsoncTrivia(next, 0);
|
|
480
|
+
if (rootStart >= next.length || next[rootStart] !== "{")
|
|
481
|
+
return `${JSON.stringify(nextRoot, null, 2)}\n`;
|
|
482
|
+
let rootEnd = findMatchingJsoncBracket(next, rootStart);
|
|
483
|
+
if (rootEnd === -1)
|
|
484
|
+
return `${JSON.stringify(nextRoot, null, 2)}\n`;
|
|
485
|
+
const pluginProp = findJsoncObjectProperty(next, rootStart, rootEnd, OPENCODE_PLUGIN_CONFIG_KEY);
|
|
486
|
+
if (pluginProp === null) {
|
|
487
|
+
next = insertJsoncObjectProperty(next, rootEnd, OPENCODE_PLUGIN_CONFIG_KEY, nextPlugins, " ");
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
const shouldAppendPlugin = !hasPluginRegistration && nextPlugins.includes(OPENCODE_PLUGIN_PACKAGE);
|
|
491
|
+
if (shouldAppendPlugin) {
|
|
492
|
+
const arrayEnd = findMatchingJsoncBracket(next, pluginProp.valueStart);
|
|
493
|
+
if (arrayEnd !== -1) {
|
|
494
|
+
next = appendJsoncArrayString(next, arrayEnd, OPENCODE_PLUGIN_PACKAGE, " ");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
rootEnd = findMatchingJsoncBracket(next, rootStart);
|
|
499
|
+
if (rootEnd === -1)
|
|
500
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
501
|
+
const mcpProp = findJsoncObjectProperty(next, rootStart, rootEnd, "mcp");
|
|
502
|
+
if (mcpProp === null) {
|
|
503
|
+
if (mcpEntryPath === null)
|
|
504
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
505
|
+
const mcpEntry = opencodeWebpressoMcpEntry(mcpEntryPath);
|
|
506
|
+
next = insertJsoncObjectProperty(next, rootEnd, "mcp", { [OPENCODE_MCP_SERVER_NAME]: mcpEntry }, " ");
|
|
507
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
508
|
+
}
|
|
509
|
+
const mcpEnd = findMatchingJsoncBracket(next, mcpProp.valueStart);
|
|
510
|
+
if (mcpEnd === -1)
|
|
511
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
512
|
+
for (const name of removedMcpServerNames) {
|
|
513
|
+
const legacyProp = findJsoncObjectProperty(next, mcpProp.valueStart, mcpEnd, name);
|
|
514
|
+
if (legacyProp !== null) {
|
|
515
|
+
next = removeJsoncObjectProperty(next, legacyProp, mcpProp.valueStart);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const nextMcpEnd = findMatchingJsoncBracket(next, mcpProp.valueStart);
|
|
519
|
+
if (nextMcpEnd === -1)
|
|
520
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
521
|
+
if (mcpEntryPath === null)
|
|
522
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
523
|
+
const mcpEntry = opencodeWebpressoMcpEntry(mcpEntryPath);
|
|
524
|
+
const webpressoProp = findJsoncObjectProperty(next, mcpProp.valueStart, nextMcpEnd, OPENCODE_MCP_SERVER_NAME);
|
|
525
|
+
if (webpressoProp === null) {
|
|
526
|
+
next = insertJsoncObjectProperty(next, nextMcpEnd, OPENCODE_MCP_SERVER_NAME, mcpEntry, " ");
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
const rendered = JSON.stringify(mcpEntry, null, 2)
|
|
530
|
+
.split("\n")
|
|
531
|
+
.map((line, index) => (index === 0 ? line : ` ${line}`))
|
|
532
|
+
.join("\n");
|
|
533
|
+
next = `${next.slice(0, webpressoProp.valueStart)}${rendered}${next.slice(webpressoProp.valueEnd)}`;
|
|
534
|
+
}
|
|
535
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
536
|
+
}
|
|
537
|
+
function defaultOpenCodeConfigPath(repoRoot) {
|
|
538
|
+
const jsonPath = join(repoRoot, "opencode.json");
|
|
539
|
+
if (existsSync(jsonPath))
|
|
540
|
+
return jsonPath;
|
|
541
|
+
const jsoncPath = join(repoRoot, "opencode.jsonc");
|
|
542
|
+
if (existsSync(jsoncPath))
|
|
543
|
+
return jsoncPath;
|
|
544
|
+
return jsonPath;
|
|
545
|
+
}
|
|
77
546
|
export function ensureOpenCodeWebpressoPlugin(input) {
|
|
78
|
-
const configPath = input.configPath ??
|
|
547
|
+
const configPath = input.configPath ?? defaultOpenCodeConfigPath(input.repoRoot);
|
|
79
548
|
if (input.options.dryRun) {
|
|
80
549
|
return { kind: "opencode-webpresso-plugin-skipped-dry-run", path: configPath };
|
|
81
550
|
}
|
|
551
|
+
return withScaffolderFileLock(configPath, () => ensureOpenCodeWebpressoPluginLocked(input, configPath));
|
|
552
|
+
}
|
|
553
|
+
function ensureOpenCodeWebpressoPluginLocked(input, configPath) {
|
|
82
554
|
const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
555
|
+
if (hasDuplicateManagedJsoncKeys(existing, configPath)) {
|
|
556
|
+
return { kind: "opencode-webpresso-plugin-invalid-config", path: configPath };
|
|
557
|
+
}
|
|
83
558
|
let parsed;
|
|
84
559
|
try {
|
|
85
|
-
parsed = existing
|
|
560
|
+
parsed = parseOpenCodeConfig(existing, configPath);
|
|
86
561
|
}
|
|
87
562
|
catch {
|
|
88
563
|
return { kind: "opencode-webpresso-plugin-invalid-json", path: configPath };
|
|
@@ -93,15 +568,22 @@ export function ensureOpenCodeWebpressoPlugin(input) {
|
|
|
93
568
|
if (parsed.mcp !== undefined && !isJsonRecord(parsed.mcp)) {
|
|
94
569
|
return { kind: "opencode-webpresso-plugin-invalid-config", path: configPath };
|
|
95
570
|
}
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
571
|
+
const resolution = input.entryPath === undefined ? resolveWebpressoMcpEntry(input.probe) : undefined;
|
|
572
|
+
const mcpEntryPath = input.entryPath ?? resolution?.entryPath ?? null;
|
|
573
|
+
// When we can author the canonical `mcp.webpresso` entry we OWN the alias:
|
|
574
|
+
// drop any prior copy silently (not a "removal") and re-author it below.
|
|
575
|
+
// When no launcher resolves, fall back to GC-only semantics on the prior mcp
|
|
576
|
+
// so a consumer's own entries (including a same-named one) stay untouched.
|
|
577
|
+
const priorMcp = isJsonRecord(parsed.mcp) ? parsed.mcp : {};
|
|
578
|
+
const mcpForCleanup = mcpEntryPath !== null
|
|
579
|
+
? Object.fromEntries(Object.entries(priorMcp).filter(([name, value]) => name !== OPENCODE_MCP_SERVER_NAME &&
|
|
580
|
+
!isKnownWebpressoOwnedLegacyMcpAlias(name, value)))
|
|
581
|
+
: Object.fromEntries(Object.entries(priorMcp).filter(([name, value]) => !isKnownWebpressoOwnedLegacyMcpAlias(name, value)));
|
|
582
|
+
const removedMcpAliases = Object.entries(priorMcp)
|
|
583
|
+
.filter(([name, value]) => isKnownWebpressoOwnedLegacyMcpAlias(name, value))
|
|
584
|
+
.map(([name]) => `mcp:${name}`)
|
|
585
|
+
.toSorted();
|
|
586
|
+
const preserved = [...collectPreservedLegacyMcpAliases(mcpForCleanup)].toSorted();
|
|
105
587
|
const pluginValue = parsed[OPENCODE_PLUGIN_CONFIG_KEY];
|
|
106
588
|
if (pluginValue !== undefined && !isStringArray(pluginValue)) {
|
|
107
589
|
return { kind: "opencode-webpresso-plugin-invalid-config", path: configPath };
|
|
@@ -115,30 +597,43 @@ export function ensureOpenCodeWebpressoPlugin(input) {
|
|
|
115
597
|
...parsed,
|
|
116
598
|
[OPENCODE_PLUGIN_CONFIG_KEY]: nextPlugins,
|
|
117
599
|
};
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
: []),
|
|
123
|
-
];
|
|
124
|
-
const nextMcp = mcpCleanup.nextMcp ?? {};
|
|
600
|
+
const nextMcp = { ...mcpForCleanup };
|
|
601
|
+
if (mcpEntryPath !== null) {
|
|
602
|
+
nextMcp[OPENCODE_MCP_SERVER_NAME] = opencodeWebpressoMcpEntry(mcpEntryPath);
|
|
603
|
+
}
|
|
125
604
|
nextRoot.mcp = nextMcp;
|
|
126
|
-
const next =
|
|
605
|
+
const next = renderOpenCodeConfig(existing, configPath, nextRoot, nextPlugins, dedupedExistingPlugins.includes(OPENCODE_PLUGIN_PACKAGE), mcpEntryPath, removedMcpAliases.map((alias) => alias.slice("mcp:".length)));
|
|
127
606
|
const configChanged = next !== existing;
|
|
128
607
|
if (configChanged) {
|
|
129
|
-
|
|
130
|
-
|
|
608
|
+
const mode = existsSync(configPath) ? statSync(configPath).mode & 0o777 : 0o600;
|
|
609
|
+
writeFileAtomicStrict(configPath, next, { encoding: "utf8", mode });
|
|
131
610
|
}
|
|
132
|
-
|
|
611
|
+
const staleHookPluginPath = join(input.repoRoot, STALE_OPENCODE_HOOK_PLUGIN_RELATIVE_PATH);
|
|
612
|
+
const staleCleanup = withScaffolderFileLock(staleHookPluginPath, () => {
|
|
613
|
+
if (!existsSync(staleHookPluginPath))
|
|
614
|
+
return { removed: [], preserved: [] };
|
|
615
|
+
if (!isWebpressoOwnedStaleHookPlugin(staleHookPluginPath)) {
|
|
616
|
+
return {
|
|
617
|
+
removed: [],
|
|
618
|
+
preserved: [`stale-hook-plugin:${STALE_OPENCODE_HOOK_PLUGIN_RELATIVE_PATH}`],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
133
621
|
rmSync(staleHookPluginPath, { force: true });
|
|
134
|
-
|
|
622
|
+
return {
|
|
623
|
+
removed: [`stale-hook-plugin:${STALE_OPENCODE_HOOK_PLUGIN_RELATIVE_PATH}`],
|
|
624
|
+
preserved: [],
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
const removed = [...removedMcpAliases, ...staleCleanup.removed].toSorted();
|
|
628
|
+
const finalPreserved = [...preserved, ...staleCleanup.preserved].toSorted();
|
|
135
629
|
if (configChanged || removed.length > 0) {
|
|
136
630
|
return {
|
|
137
631
|
kind: "opencode-webpresso-plugin-written",
|
|
138
632
|
path: configPath,
|
|
139
633
|
pluginPackage: OPENCODE_PLUGIN_PACKAGE,
|
|
140
634
|
removed: removed.toSorted(),
|
|
141
|
-
preserved,
|
|
635
|
+
preserved: finalPreserved,
|
|
636
|
+
mcpEntryPath,
|
|
142
637
|
};
|
|
143
638
|
}
|
|
144
639
|
return {
|
|
@@ -146,6 +641,7 @@ export function ensureOpenCodeWebpressoPlugin(input) {
|
|
|
146
641
|
path: configPath,
|
|
147
642
|
pluginPackage: OPENCODE_PLUGIN_PACKAGE,
|
|
148
643
|
removed: [],
|
|
149
|
-
preserved,
|
|
644
|
+
preserved: finalPreserved,
|
|
645
|
+
mcpEntryPath,
|
|
150
646
|
};
|
|
151
647
|
}
|