@webpresso/agent-kit 3.3.3 → 3.3.4
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/THIRD-PARTY-NOTICES.md +12 -10
- package/catalog/agent/rules/changeset-release.md +15 -4
- package/catalog/agent/rules/managed-tool-binaries.md +67 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
- package/catalog/agent/rules/pre-implementation.md +36 -20
- package/catalog/agent/rules/rtk-routing.md +22 -16
- package/catalog/agent/skills/autopilot/SKILL.md +4 -3
- package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
- package/dist/esm/audit/hook-surface.js +5 -1
- package/dist/esm/audit/repo-guardrails.d.ts +8 -0
- package/dist/esm/audit/repo-guardrails.js +71 -1
- package/dist/esm/blueprint/core/parser.js +38 -12
- package/dist/esm/blueprint/execution/artifacts.js +8 -4
- package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
- package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
- package/dist/esm/blueprint/execution/metadata.js +8 -4
- package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
- package/dist/esm/blueprint/lifecycle/audit.js +89 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +63 -10
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
- package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
- package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
- package/dist/esm/blueprint/markdown/helpers.js +10 -4
- package/dist/esm/blueprint/tracked-document/parser.js +3 -3
- package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
- package/dist/esm/blueprint/trust/gate-row.js +113 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
- package/dist/esm/blueprint/trust/promotion.js +19 -19
- package/dist/esm/build/atomic-file-copy.d.ts +32 -0
- package/dist/esm/build/atomic-file-copy.js +44 -0
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
- package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
- package/dist/esm/cli/commands/compile.d.ts +13 -0
- package/dist/esm/cli/commands/compile.js +182 -62
- package/dist/esm/cli/commands/format.js +17 -1
- package/dist/esm/cli/commands/gain/index.js +10 -3
- package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
- package/dist/esm/cli/commands/init/index.js +29 -48
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +48 -13
- package/dist/esm/cli/commands/init/mcp-spec.js +86 -30
- package/dist/esm/cli/commands/init/merge.js +30 -10
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
- package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
- package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
- package/dist/esm/cli/commands/pr-help.js +1 -1
- package/dist/esm/cli/commands/pr.js +18 -4
- package/dist/esm/cli/commands/review.js +60 -6
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
- package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
- package/dist/esm/cli/commands/worktree/router.js +5 -2
- package/dist/esm/cli/direct-provider-launch.js +12 -4
- package/dist/esm/compiler/flatten.d.ts +10 -1
- package/dist/esm/compiler/flatten.js +14 -4
- package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
- package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
- package/dist/esm/compose/compose.d.ts +8 -0
- package/dist/esm/compose/compose.js +115 -8
- package/dist/esm/compose/ops-report-schema.js +17 -3
- package/dist/esm/compose/registry-resolve.d.ts +17 -0
- package/dist/esm/compose/registry-resolve.js +78 -11
- package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
- package/dist/esm/config/oxlint/import-hygiene.js +21 -8
- package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
- package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
- package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
- package/dist/esm/config/oxlint/oxlintrc.js +18 -21
- package/dist/esm/config/oxlint/path-roles.js +53 -0
- package/dist/esm/config/oxlint/query-patterns.js +2 -5
- package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
- package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
- package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
- package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
- package/dist/esm/daemon/domains/launch.d.ts +25 -0
- package/dist/esm/daemon/domains/launch.js +30 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
- package/dist/esm/daemon/handlers/product.d.ts +10 -0
- package/dist/esm/daemon/handlers/product.js +10 -1
- package/dist/esm/daemon/methods.d.ts +21 -1
- package/dist/esm/daemon/methods.js +20 -0
- package/dist/esm/daemon/protocol/contract.d.ts +58 -0
- package/dist/esm/daemon/protocol/contract.js +38 -0
- package/dist/esm/daemon/server.js +7 -0
- package/dist/esm/docs-linter/blueprint-plan.js +2 -2
- package/dist/esm/errors/wp-error.js +21 -2
- package/dist/esm/hooks/doctor.d.ts +11 -0
- package/dist/esm/hooks/doctor.js +114 -45
- package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
- package/dist/esm/hooks/pretool-guard/runner.js +6 -0
- package/dist/esm/hooks/status/index.js +1 -1
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
- package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
- package/dist/esm/mcp/blueprint/registration.js +1 -1
- package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
- package/dist/esm/mcp/tools/_shared/redact.js +114 -8
- package/dist/esm/mcp/tools/gain.js +28 -2
- package/dist/esm/mcp/tools/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-compose.js +38 -4
- package/dist/esm/mcp/tools/wp-ui-preview.js +14 -7
- package/dist/esm/review/lifecycle-writers.d.ts +76 -0
- package/dist/esm/review/lifecycle-writers.js +330 -0
- package/dist/esm/review/subject.js +66 -32
- package/dist/esm/status/snapshot.d.ts +5 -0
- package/dist/esm/status/snapshot.js +27 -2
- package/dist/esm/test/shard-durations.json +0 -1
- package/dist/esm/test-helpers/global-setup.js +38 -3
- package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
- package/dist/esm/tool-runtime/managed-rtk.js +275 -0
- package/dist/esm/tool-runtime/resolve-runner.js +18 -194
- package/dist/esm/utils/package-root.d.ts +32 -0
- package/dist/esm/utils/package-root.js +82 -0
- package/dist/esm/worktrees/git-metadata.d.ts +41 -0
- package/dist/esm/worktrees/git-metadata.js +48 -0
- package/package.json +16 -21
- package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
- package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
- package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
- package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
- package/dist/esm/config/oxlint/index.d.ts +0 -22
- package/dist/esm/config/oxlint/index.js +0 -28
- package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
- package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
- package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
- package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
- package/dist/esm/output-transforms/rulesync.d.ts +0 -2
- package/dist/esm/output-transforms/rulesync.js +0 -79
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
* One source of truth for how every supported host launches the webpresso MCP
|
|
5
5
|
* server. Each host channel (Claude plugin manifest, Codex plugin manifest,
|
|
6
6
|
* Codex `config.toml`, OpenCode `opencode.json`, Grok `.grok/config.toml`)
|
|
7
|
-
* projects this spec into its own config dialect. The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* projects this spec into its own config dialect. The Claude plugin manifest
|
|
8
|
+
* references the launcher via `${CLAUDE_PLUGIN_ROOT}`, which Claude Code
|
|
9
|
+
* expands. Codex does **not** expand `${PLUGIN_ROOT}` in a bundled `.mcp.json`
|
|
10
|
+
* — that variable reaches hook commands only — and it spawns bundled MCP
|
|
11
|
+
* servers with the *project* cwd and no plugin env, so the Codex manifest can
|
|
12
|
+
* anchor on neither the plugin root nor a relative path and uses the bare
|
|
13
|
+
* `wp` name resolved from PATH at spawn time. The remaining setup-time writers
|
|
14
|
+
* (Grok, OpenCode) resolve an absolute `bin/wp` path via
|
|
15
|
+
* {@link resolveWebpressoMcpEntry}. Compare channels *semantically* (command
|
|
11
16
|
* basename, args, env) — never byte-for-byte.
|
|
12
17
|
*/
|
|
13
18
|
import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
@@ -17,6 +22,8 @@ import { execFileSync } from "node:child_process";
|
|
|
17
22
|
import { fileURLToPath } from "node:url";
|
|
18
23
|
import { isVersionedAgentKitPayloadPath } from "#cli/auto-update/self-invocation.js";
|
|
19
24
|
import { pathCandidates } from "#runtime/command-exists.js";
|
|
25
|
+
import { isManagedWorktreePath, isPathContained } from "#worktrees/identity.js";
|
|
26
|
+
import { resolveKnownManagedWorktreeRoots } from "#worktrees/location.js";
|
|
20
27
|
export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
|
|
21
28
|
/**
|
|
22
29
|
* The single canonical launch spec for the webpresso MCP server. `name` is the
|
|
@@ -63,26 +70,6 @@ const WP_LAUNCHER_REQUIRED_RELATIVES = [
|
|
|
63
70
|
join("bin", "runtime-lanes.js"),
|
|
64
71
|
join("bin", "runtime-manifest.json"),
|
|
65
72
|
];
|
|
66
|
-
/**
|
|
67
|
-
* Resolve the absolute path to webpresso's global `bin/wp` launcher on this
|
|
68
|
-
* machine. Probes the locations consumers use to install webpresso, in order
|
|
69
|
-
* of stability:
|
|
70
|
-
*
|
|
71
|
-
* 1. the stable global `wp` shim available on PATH (including the
|
|
72
|
-
* vite-plus multi-package shim at `~/.vite-plus/bin/wp`)
|
|
73
|
-
* 2. the currently executing `@webpresso/agent-kit` package **only when it
|
|
74
|
-
* is not a vite-plus `agent-kit#<installId>` payload path**
|
|
75
|
-
* 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
|
|
76
|
-
* 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
|
|
77
|
-
*
|
|
78
|
-
* Host MCP configs (Grok/Codex/etc.) must never be pinned to a versioned
|
|
79
|
-
* installId tree: the next `wp update` / `vp update -g` deletes that path and
|
|
80
|
-
* leaves every host with a dead launcher. Returns `null` when none of the
|
|
81
|
-
* candidates yield a durable `wp`.
|
|
82
|
-
*/
|
|
83
|
-
export function findWebpressoMcpEntry(probe = {}) {
|
|
84
|
-
return resolveWebpressoMcpEntry(probe).entryPath;
|
|
85
|
-
}
|
|
86
73
|
function isUsableWebpressoMcpRoot(root) {
|
|
87
74
|
return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
|
|
88
75
|
}
|
|
@@ -117,6 +104,23 @@ export function isVitePlusManagedWebpressoWp(entryPath) {
|
|
|
117
104
|
function isDurableMcpEntryPath(entryPath) {
|
|
118
105
|
return !isVersionedAgentKitPayloadPath(entryPath);
|
|
119
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve the absolute path to webpresso's global `bin/wp` launcher on this
|
|
109
|
+
* machine. Probes the locations consumers use to install webpresso, in order
|
|
110
|
+
* of stability:
|
|
111
|
+
*
|
|
112
|
+
* 1. the stable global `wp` shim available on PATH (including the
|
|
113
|
+
* vite-plus multi-package shim at `~/.vite-plus/bin/wp`)
|
|
114
|
+
* 2. the currently executing `@webpresso/agent-kit` package **only when it
|
|
115
|
+
* is not a vite-plus `agent-kit#<installId>` payload path**
|
|
116
|
+
* 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
|
|
117
|
+
* 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
|
|
118
|
+
*
|
|
119
|
+
* Host MCP configs (Grok/OpenCode) must never be pinned to a versioned
|
|
120
|
+
* installId tree: the next `wp update` / `vp update -g` deletes that path and
|
|
121
|
+
* leaves every host with a dead launcher. `entryPath` is `null` when none of
|
|
122
|
+
* the candidates yield a durable `wp`.
|
|
123
|
+
*/
|
|
120
124
|
export function resolveWebpressoMcpEntry(probe = {}) {
|
|
121
125
|
const checked = [];
|
|
122
126
|
if (probe.candidates === undefined) {
|
|
@@ -151,8 +155,9 @@ export function resolveWebpressoMcpEntry(probe = {}) {
|
|
|
151
155
|
// Never materialize host MCP configs against a vite-plus installId tree.
|
|
152
156
|
if (!isDurableMcpEntryPath(wpBin))
|
|
153
157
|
continue;
|
|
154
|
-
if (isTrustedWebpressoPackageRoot(root))
|
|
158
|
+
if (isTrustedWebpressoPackageRoot(root, probe.sourceRoot)) {
|
|
155
159
|
return { entryPath: wpBin, checked };
|
|
160
|
+
}
|
|
156
161
|
}
|
|
157
162
|
}
|
|
158
163
|
return { entryPath: null, checked };
|
|
@@ -200,27 +205,78 @@ function isPathInsideOrEqual(parent, candidate) {
|
|
|
200
205
|
const rel = relative(resolve(parentRoot), resolve(candidateRoot));
|
|
201
206
|
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
202
207
|
}
|
|
203
|
-
function isTrustedWebpressoPackageRoot(packageRoot) {
|
|
208
|
+
function isTrustedWebpressoPackageRoot(packageRoot, resolveSourceRoot = currentAgentKitPackageRoot) {
|
|
204
209
|
try {
|
|
205
210
|
if (!isUsableWebpressoPackageRoot(packageRoot))
|
|
206
211
|
return false;
|
|
207
212
|
const realPackageRoot = realpathSync(packageRoot);
|
|
208
|
-
const sourceRoot =
|
|
209
|
-
|
|
213
|
+
const sourceRoot = resolveSourceRoot();
|
|
214
|
+
// A managed blueprint worktree is deleted by `wp worktree merge-cleanup`
|
|
215
|
+
// once its PR lands, so self-trusting it here would persist a path into
|
|
216
|
+
// host MCP configs that later resolves to nothing. Only the durable
|
|
217
|
+
// primary checkout may earn self-trust.
|
|
218
|
+
const home = process.env.HOME || homedir();
|
|
219
|
+
const isManagedWorktree = isManagedWorktreePath(realPackageRoot, resolveKnownManagedWorktreeRoots(home));
|
|
220
|
+
if (sourceRoot !== null && !isManagedWorktree && realPackageRoot === realpathSync(sourceRoot)) {
|
|
210
221
|
return true;
|
|
222
|
+
}
|
|
211
223
|
return trustedHomeInstallPackageRoots().some((anchor) => existsSync(anchor) && isPathInsideOrEqual(anchor, realPackageRoot));
|
|
212
224
|
}
|
|
213
225
|
catch {
|
|
214
226
|
return false;
|
|
215
227
|
}
|
|
216
228
|
}
|
|
217
|
-
function trustedHomeInstallPackageRoots() {
|
|
218
|
-
const home = process.env.HOME || homedir();
|
|
229
|
+
function trustedHomeInstallPackageRoots(home = process.env.HOME || homedir()) {
|
|
219
230
|
return [
|
|
220
231
|
join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit"),
|
|
221
232
|
join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit"),
|
|
222
233
|
];
|
|
223
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Every root under which agent-kit itself installs (or checks out) a `bin/wp`.
|
|
237
|
+
* A `wp` launcher recorded inside one of these was written by agent-kit; a
|
|
238
|
+
* launcher anywhere else is an operator's deliberate choice.
|
|
239
|
+
*/
|
|
240
|
+
export function agentKitOwnedCommandRoots(probe = {}) {
|
|
241
|
+
const home = probe.home ?? process.env.HOME ?? homedir();
|
|
242
|
+
const sourceRoot = (probe.sourceRoot ?? currentAgentKitPackageRoot)();
|
|
243
|
+
return [
|
|
244
|
+
...trustedHomeInstallPackageRoots(home),
|
|
245
|
+
join(home, ".vite-plus"),
|
|
246
|
+
...resolveKnownManagedWorktreeRoots(home),
|
|
247
|
+
...(sourceRoot === null ? [] : [sourceRoot]),
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
const WP_LAUNCHER_BASENAMES = new Set(["wp", "wp.exe"]);
|
|
251
|
+
/**
|
|
252
|
+
* Ownership — not shape — decides whether setup may retire a persisted MCP
|
|
253
|
+
* entry. An entry is agent-kit's own when it launches `wp mcp` from an
|
|
254
|
+
* ABSOLUTE path AND either that path is gone (one we would have deleted) or
|
|
255
|
+
* it lives inside one
|
|
256
|
+
* of {@link agentKitOwnedCommandRoots}. A *live* `wp mcp` launcher outside
|
|
257
|
+
* those roots is an operator pointing Codex at a custom build: preserve it.
|
|
258
|
+
*/
|
|
259
|
+
export function isAgentKitOwnedWpCommand(command, args, probe = {}) {
|
|
260
|
+
if (!WP_LAUNCHER_BASENAMES.has(basename(command).toLowerCase()))
|
|
261
|
+
return false;
|
|
262
|
+
if (args.length !== WEBPRESSO_MCP_SPEC.args.length)
|
|
263
|
+
return false;
|
|
264
|
+
if (args.some((value, index) => value !== WEBPRESSO_MCP_SPEC.args[index]))
|
|
265
|
+
return false;
|
|
266
|
+
// Every agent-kit writer of this block persisted an ABSOLUTE launcher path
|
|
267
|
+
// (see resolveWebpressoMcpEntry). A bare PATH name — the spelling the Codex
|
|
268
|
+
// plugin manifest itself ships — or a cwd-relative path is therefore
|
|
269
|
+
// operator-authored, and its meaning depends on a PATH/cwd this predicate
|
|
270
|
+
// cannot reconstruct. Preserve it and let the doctor report it.
|
|
271
|
+
if (!isAbsolute(command))
|
|
272
|
+
return false;
|
|
273
|
+
const exists = probe.exists ?? existsSync;
|
|
274
|
+
// Only an absolute path can have been written by us and then rotted away
|
|
275
|
+
// (deleted managed worktree, replaced versioned install tree).
|
|
276
|
+
if (!exists(command))
|
|
277
|
+
return true;
|
|
278
|
+
return agentKitOwnedCommandRoots(probe).some((root) => isPathContained(root, command));
|
|
279
|
+
}
|
|
224
280
|
function packageWpFromAdjacentNpmCmdShim(entryPath) {
|
|
225
281
|
if (!entryPath.toLowerCase().endsWith(".cmd"))
|
|
226
282
|
return null;
|
|
@@ -9,10 +9,21 @@
|
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
|
|
10
10
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
11
11
|
import { convergeUnitFromTarget } from "./convergence.js";
|
|
12
|
-
import { resolveUntrackedCollision } from "./untracked-collision.js";
|
|
13
|
-
|
|
12
|
+
import { isGitIgnored, resolveUntrackedCollision } from "./untracked-collision.js";
|
|
13
|
+
/**
|
|
14
|
+
* Guard an overwrite against clobbering unknown untracked content. Returns a
|
|
15
|
+
* warning to surface to the caller when the write proceeded past divergent
|
|
16
|
+
* content it could safely recover from (git-ignored lane only); throws when
|
|
17
|
+
* the write must be blocked.
|
|
18
|
+
*
|
|
19
|
+
* `patchDerived` must be true only when `incoming` was computed FROM the
|
|
20
|
+
* target's existing content (e.g. `patchJsonFile`'s key-preserving patch) —
|
|
21
|
+
* that is what makes it safe to write through divergent unmarked content
|
|
22
|
+
* without a backup.
|
|
23
|
+
*/
|
|
24
|
+
function guardUntrackedOverwrite(targetPath, incoming, opts, patchDerived) {
|
|
14
25
|
if (!opts.repoRoot)
|
|
15
|
-
return;
|
|
26
|
+
return undefined;
|
|
16
27
|
const repoRoot = resolve(opts.repoRoot);
|
|
17
28
|
const target = resolve(targetPath);
|
|
18
29
|
const repoRelative = relative(repoRoot, target);
|
|
@@ -20,19 +31,25 @@ function guardUntrackedOverwrite(targetPath, incoming, opts) {
|
|
|
20
31
|
repoRelative === ".." ||
|
|
21
32
|
repoRelative.startsWith(`..${sep}`) ||
|
|
22
33
|
isAbsolute(repoRelative)) {
|
|
23
|
-
return;
|
|
34
|
+
return undefined;
|
|
24
35
|
}
|
|
36
|
+
const gitPath = repoRelative.split(sep).join("/");
|
|
37
|
+
const scope = isGitIgnored(repoRoot, gitPath) ? "repo-ignored" : "repo-committed";
|
|
25
38
|
const result = resolveUntrackedCollision({
|
|
26
39
|
unit: convergeUnitFromTarget(repoRoot, target, {
|
|
27
40
|
action: "modify",
|
|
28
|
-
scope
|
|
41
|
+
scope,
|
|
29
42
|
reason: "setup merge target",
|
|
30
43
|
}),
|
|
31
44
|
desiredContent: incoming,
|
|
32
45
|
force: opts.overwrite,
|
|
46
|
+
patchDerived,
|
|
33
47
|
});
|
|
34
48
|
if (!result.ok)
|
|
35
49
|
throw new Error(result.error);
|
|
50
|
+
if (result.warning)
|
|
51
|
+
console.error(`wp setup: ${result.warning}`);
|
|
52
|
+
return result.warning;
|
|
36
53
|
}
|
|
37
54
|
export function writeFileMerged(targetPath, incoming, opts = {}) {
|
|
38
55
|
const exists = existsSync(targetPath);
|
|
@@ -51,9 +68,9 @@ export function writeFileMerged(targetPath, incoming, opts = {}) {
|
|
|
51
68
|
if (shouldOverwrite) {
|
|
52
69
|
if (opts.dryRun)
|
|
53
70
|
return { targetPath, action: "skipped-dry" };
|
|
54
|
-
guardUntrackedOverwrite(targetPath, incoming, opts);
|
|
71
|
+
const warning = guardUntrackedOverwrite(targetPath, incoming, opts, false);
|
|
55
72
|
writeFileSync(targetPath, incoming);
|
|
56
|
-
return { targetPath, action: "overwritten" };
|
|
73
|
+
return { targetPath, action: "overwritten", ...(warning ? { note: warning } : {}) };
|
|
57
74
|
}
|
|
58
75
|
if (opts.dryRun)
|
|
59
76
|
return { targetPath, action: "skipped-dry" };
|
|
@@ -119,11 +136,14 @@ export function patchJsonFile(targetPath, patcher, opts = {}) {
|
|
|
119
136
|
if (opts.dryRun) {
|
|
120
137
|
return { targetPath, action: "skipped-dry" };
|
|
121
138
|
}
|
|
122
|
-
|
|
123
|
-
guardUntrackedOverwrite(targetPath, incoming, opts);
|
|
139
|
+
const warning = exists ? guardUntrackedOverwrite(targetPath, incoming, opts, true) : undefined;
|
|
124
140
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
125
141
|
writeFileSync(targetPath, incoming);
|
|
126
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
targetPath,
|
|
144
|
+
action: exists ? "overwritten" : "created",
|
|
145
|
+
...(warning ? { note: warning } : {}),
|
|
146
|
+
};
|
|
127
147
|
}
|
|
128
148
|
export function summarizeResults(results) {
|
|
129
149
|
const summary = {
|
|
@@ -6,21 +6,18 @@
|
|
|
6
6
|
* the patch tiny and deterministic: per-server upserts, no TOML parser
|
|
7
7
|
* dependency, no edits to unrelated user config.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 2. Consumer-owned `.mcp.json` package declarations — compiled to setup-
|
|
18
|
-
* provisioned, exact-version binaries with environment-variable names
|
|
19
|
-
* forwarded through Codex's stdio `env_vars` allow-list.
|
|
9
|
+
* Webpresso's own MCP server is NOT one of them. Codex receives `webpresso`
|
|
10
|
+
* over exactly one delivery channel — the bundled Codex plugin's `.mcp.json`.
|
|
11
|
+
* Setup therefore *retires* the `[mcp_servers.webpresso]` block earlier
|
|
12
|
+
* versions wrote here (see {@link pruneLegacyCodexWebpressoMcp}) instead of
|
|
13
|
+
* refreshing it. What remains managed here is consumer-owned `.mcp.json`
|
|
14
|
+
* package declarations — compiled to setup-provisioned, exact-version binaries
|
|
15
|
+
* with environment-variable names forwarded through Codex's stdio `env_vars`
|
|
16
|
+
* allow-list.
|
|
20
17
|
*/
|
|
21
18
|
import { spawnSync } from "node:child_process";
|
|
22
|
-
import {
|
|
23
|
-
export {
|
|
19
|
+
import { WEBPRESSO_MCP_SERVER_NAME, type AgentKitOwnedCommandProbe, type WebpressoInstallProbe } from "#cli/commands/init/mcp-spec.js";
|
|
20
|
+
export { WEBPRESSO_MCP_SERVER_NAME };
|
|
24
21
|
export type { WebpressoInstallProbe };
|
|
25
22
|
import type { MergeOptions } from "#cli/commands/init/merge";
|
|
26
23
|
import { type PinnedCodexMcpDefinition } from "#hooks/host-config-contract.js";
|
|
@@ -143,35 +140,63 @@ export type EnsureClaudePlaywrightMcpResult = {
|
|
|
143
140
|
path: string;
|
|
144
141
|
};
|
|
145
142
|
export declare function ensureClaudePlaywrightMcp(input: EnsureClaudePlaywrightMcpInput): EnsureClaudePlaywrightMcpResult;
|
|
146
|
-
export
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
export type PruneOwnedMcpServerOutcome = {
|
|
144
|
+
readonly kind: "removed";
|
|
145
|
+
readonly raw: string;
|
|
146
|
+
readonly command: string;
|
|
147
|
+
} | {
|
|
148
|
+
readonly kind: "absent";
|
|
149
|
+
readonly raw: string;
|
|
150
|
+
} | {
|
|
151
|
+
readonly kind: "preserved";
|
|
152
|
+
readonly raw: string;
|
|
153
|
+
readonly command: string | null;
|
|
154
|
+
} | {
|
|
155
|
+
readonly kind: "ambiguous";
|
|
156
|
+
readonly raw: string;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Remove `[mcp_servers.<serverId>]` (plus its contiguous sub-tables) when
|
|
160
|
+
* `isOwned` claims the block's launch tuple. Fails closed — leaving `raw`
|
|
161
|
+
* byte-identical — for every TOML spelling this line scanner cannot rewrite
|
|
162
|
+
* safely (dotted keys, inline parent table, array tables, non-contiguous
|
|
163
|
+
* sub-tables).
|
|
164
|
+
*/
|
|
165
|
+
export declare function pruneOwnedMcpServerBlock(raw: string, serverId: string, isOwned: (command: string, args: readonly string[]) => boolean): PruneOwnedMcpServerOutcome;
|
|
166
|
+
export interface PruneLegacyCodexWebpressoMcpInput {
|
|
149
167
|
options: MergeOptions;
|
|
150
|
-
/** Test seam — override the resolved MCP entry path. */
|
|
151
|
-
entryPath?: string;
|
|
152
168
|
/** Test seam — override `$CODEX_HOME/config.toml`. */
|
|
153
169
|
configPath?: string;
|
|
154
|
-
/** Test seam — override
|
|
155
|
-
|
|
170
|
+
/** Test seam — override agent-kit install-root ownership resolution. */
|
|
171
|
+
ownership?: AgentKitOwnedCommandProbe;
|
|
156
172
|
}
|
|
157
|
-
export type
|
|
158
|
-
kind: "codex-webpresso-mcp-
|
|
173
|
+
export type PruneLegacyCodexWebpressoMcpResult = {
|
|
174
|
+
kind: "codex-legacy-webpresso-mcp-removed";
|
|
159
175
|
path: string;
|
|
160
|
-
|
|
176
|
+
command: string;
|
|
161
177
|
} | {
|
|
162
|
-
kind: "codex-webpresso-mcp-
|
|
178
|
+
kind: "codex-legacy-webpresso-mcp-absent";
|
|
163
179
|
path: string;
|
|
164
|
-
entryPath: string;
|
|
165
180
|
} | {
|
|
166
|
-
kind: "codex-webpresso-mcp-
|
|
181
|
+
kind: "codex-legacy-webpresso-mcp-preserved";
|
|
167
182
|
path: string;
|
|
168
|
-
|
|
183
|
+
command: string | null;
|
|
169
184
|
} | {
|
|
170
|
-
kind: "codex-webpresso-mcp-
|
|
185
|
+
kind: "codex-legacy-webpresso-mcp-ambiguous";
|
|
171
186
|
path: string;
|
|
172
187
|
} | {
|
|
173
|
-
kind: "codex-webpresso-mcp-
|
|
188
|
+
kind: "codex-legacy-webpresso-mcp-skipped-dry-run";
|
|
174
189
|
path: string;
|
|
175
|
-
checked: readonly string[];
|
|
176
190
|
};
|
|
177
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Retire the legacy user-global `[mcp_servers.webpresso]` block. Runs from
|
|
193
|
+
* setup AND from the `wp codex` launch preflight — the launch path is the only
|
|
194
|
+
* one that reaches machines that never re-run setup.
|
|
195
|
+
*/
|
|
196
|
+
export declare function pruneLegacyCodexWebpressoMcp(input: PruneLegacyCodexWebpressoMcpInput): PruneLegacyCodexWebpressoMcpResult;
|
|
197
|
+
/**
|
|
198
|
+
* Installed Codex plugin MCP manifests — the surviving delivery channel.
|
|
199
|
+
* Cheap directory probe only: no `codex plugin` subprocess belongs on the
|
|
200
|
+
* launch hot path.
|
|
201
|
+
*/
|
|
202
|
+
export declare function findInstalledCodexPluginMcpManifests(homeDir?: string): readonly string[];
|
|
@@ -6,26 +6,23 @@
|
|
|
6
6
|
* the patch tiny and deterministic: per-server upserts, no TOML parser
|
|
7
7
|
* dependency, no edits to unrelated user config.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 2. Consumer-owned `.mcp.json` package declarations — compiled to setup-
|
|
18
|
-
* provisioned, exact-version binaries with environment-variable names
|
|
19
|
-
* forwarded through Codex's stdio `env_vars` allow-list.
|
|
9
|
+
* Webpresso's own MCP server is NOT one of them. Codex receives `webpresso`
|
|
10
|
+
* over exactly one delivery channel — the bundled Codex plugin's `.mcp.json`.
|
|
11
|
+
* Setup therefore *retires* the `[mcp_servers.webpresso]` block earlier
|
|
12
|
+
* versions wrote here (see {@link pruneLegacyCodexWebpressoMcp}) instead of
|
|
13
|
+
* refreshing it. What remains managed here is consumer-owned `.mcp.json`
|
|
14
|
+
* package declarations — compiled to setup-provisioned, exact-version binaries
|
|
15
|
+
* with environment-variable names forwarded through Codex's stdio `env_vars`
|
|
16
|
+
* allow-list.
|
|
20
17
|
*/
|
|
21
18
|
import { spawnSync } from "node:child_process";
|
|
22
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
19
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
23
20
|
import { homedir } from "node:os";
|
|
24
21
|
import { dirname, join } from "node:path";
|
|
25
|
-
import {
|
|
26
|
-
// Re-exported so existing importers
|
|
27
|
-
// implementation
|
|
28
|
-
export {
|
|
22
|
+
import { isAgentKitOwnedWpCommand, resolveExecutableOnPath, WEBPRESSO_MCP_SERVER_NAME, } from "#cli/commands/init/mcp-spec.js";
|
|
23
|
+
// Re-exported so existing importers keep a stable path; the canonical
|
|
24
|
+
// implementation lives in `mcp-spec.ts`.
|
|
25
|
+
export { WEBPRESSO_MCP_SERVER_NAME };
|
|
29
26
|
import { updateScaffolderTextFile } from "#cli/commands/init/scaffolders/file-transaction.js";
|
|
30
27
|
import { isJsonRecord, isUnsafeRuntimeLauncher, parseJson, parseProjectMcpServers, parseTomlString, } from "#hooks/host-config-contract.js";
|
|
31
28
|
import { appendGlobalCapableVpArgs, resolveGlobalCapableVpCommand, } from "#cli/global-vp.js";
|
|
@@ -309,8 +306,10 @@ function hasDottedServerDefinition(lines, startsInsideMultiline, serverId) {
|
|
|
309
306
|
export function hasAmbiguousMcpServerDottedDefinition(raw, serverId) {
|
|
310
307
|
const lines = raw.trimEnd().split(/\r?\n/);
|
|
311
308
|
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
312
|
-
|
|
313
|
-
|
|
309
|
+
// Scanned unconditionally, not only when no table header exists: a dotted key
|
|
310
|
+
// alongside the table would outlive the table's removal and re-declare the
|
|
311
|
+
// server with no launcher, which breaks the whole config load.
|
|
312
|
+
return hasDottedServerDefinition(lines, startsInsideMultiline, serverId);
|
|
314
313
|
}
|
|
315
314
|
export function hasManagedMcpServerTableConflict(raw, serverId) {
|
|
316
315
|
const lines = raw.trimEnd().split(/\r?\n/);
|
|
@@ -658,39 +657,177 @@ export function ensureClaudePlaywrightMcp(input) {
|
|
|
658
657
|
return { kind: "claude-playwright-mcp-written", path: configPath };
|
|
659
658
|
}
|
|
660
659
|
// ────────────────────────────────────────────────────────────────────────────
|
|
661
|
-
//
|
|
660
|
+
// Legacy `[mcp_servers.webpresso]` retirement
|
|
661
|
+
//
|
|
662
|
+
// Earlier versions delivered webpresso to Codex over a SECOND channel: a
|
|
663
|
+
// `[mcp_servers.webpresso]` block in the user-global `config.toml` pinned to
|
|
664
|
+
// whatever absolute install path existed at setup time. The bundled Codex
|
|
665
|
+
// plugin is now the single channel, so setup must remove what it used to write
|
|
666
|
+
// — otherwise every machine keeps a duplicate registration pinned to a path
|
|
667
|
+
// agent-kit itself deletes on the next update or worktree cleanup.
|
|
668
|
+
//
|
|
669
|
+
// The removal predicate is OWNERSHIP-based, never shape-based: a live `wp mcp`
|
|
670
|
+
// launcher outside every agent-kit-owned root is an operator pointing Codex at
|
|
671
|
+
// a custom build and is preserved (and reported by `wp doctor`).
|
|
662
672
|
// ────────────────────────────────────────────────────────────────────────────
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
673
|
+
const TOML_COMMAND_ASSIGNMENT = /^\s*command\s*=\s*(.+?)\s*$/u;
|
|
674
|
+
const TOML_ARGS_ASSIGNMENT = /^\s*args\s*=\s*(.+?)\s*$/u;
|
|
675
|
+
function firstTomlAssignmentValue(body, pattern) {
|
|
676
|
+
for (const line of body) {
|
|
677
|
+
const match = pattern.exec(line);
|
|
678
|
+
if (match?.[1] !== undefined)
|
|
679
|
+
return match[1];
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
function parseTomlStringArray(value) {
|
|
684
|
+
const trimmed = value.trim();
|
|
685
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
686
|
+
return null;
|
|
687
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
688
|
+
if (inner.length === 0)
|
|
689
|
+
return [];
|
|
690
|
+
const parsed = inner
|
|
691
|
+
.split(",")
|
|
692
|
+
.map((element) => element.trim())
|
|
693
|
+
.filter((element) => element.length > 0)
|
|
694
|
+
.map(parseTomlString);
|
|
695
|
+
return parsed.every((element) => element !== null) ? parsed : null;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Read the parent block's `command`/`args`, stopping at the first sub-table
|
|
699
|
+
* header so a `[mcp_servers.<id>.env]` child never supplies the launch tuple.
|
|
700
|
+
*/
|
|
701
|
+
function readManagedBlockLaunch(lines, startsInsideMultiline, start, end) {
|
|
702
|
+
const body = [];
|
|
703
|
+
for (let index = start + 1; index < end; index += 1) {
|
|
704
|
+
// Multiline-string content is document text, not configuration: reading a
|
|
705
|
+
// decoy tuple from it — or letting a bracket line there end the scan —
|
|
706
|
+
// would delete the operator's real registration.
|
|
707
|
+
if (startsInsideMultiline[index])
|
|
708
|
+
continue;
|
|
709
|
+
if (lines[index].trim().startsWith("["))
|
|
710
|
+
break;
|
|
711
|
+
body.push(lines[index]);
|
|
712
|
+
}
|
|
713
|
+
const commandValue = firstTomlAssignmentValue(body, TOML_COMMAND_ASSIGNMENT);
|
|
714
|
+
const command = commandValue === null ? null : parseTomlString(commandValue);
|
|
715
|
+
if (command === null)
|
|
716
|
+
return null;
|
|
717
|
+
const argsValue = firstTomlAssignmentValue(body, TOML_ARGS_ASSIGNMENT);
|
|
718
|
+
const args = argsValue === null ? [] : parseTomlStringArray(argsValue);
|
|
719
|
+
return args === null ? null : { command, args };
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* The removal path is the only one that rewrites lines it did not classify, so
|
|
723
|
+
* it must reproduce the document's existing newline style rather than impose LF.
|
|
724
|
+
*/
|
|
725
|
+
function dominantEol(raw) {
|
|
726
|
+
const crlf = raw.split("\r\n").length - 1;
|
|
727
|
+
const lf = raw.split("\n").length - 1;
|
|
728
|
+
return crlf * 2 > lf ? "\r\n" : "\n";
|
|
729
|
+
}
|
|
730
|
+
function rebuildWithoutSection(lines, start, end, eol) {
|
|
731
|
+
const kept = lines.slice(0, start);
|
|
732
|
+
while (kept.at(-1)?.trim() === "")
|
|
733
|
+
kept.pop();
|
|
734
|
+
const next = kept
|
|
735
|
+
.concat(lines.slice(end))
|
|
736
|
+
.join(eol)
|
|
737
|
+
.replace(/^(?:[ \t]*(?:\r\n|\n))+/u, "")
|
|
738
|
+
.trimEnd();
|
|
739
|
+
return next.length > 0 ? `${next}${eol}` : "";
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Remove `[mcp_servers.<serverId>]` (plus its contiguous sub-tables) when
|
|
743
|
+
* `isOwned` claims the block's launch tuple. Fails closed — leaving `raw`
|
|
744
|
+
* byte-identical — for every TOML spelling this line scanner cannot rewrite
|
|
745
|
+
* safely (dotted keys, inline parent table, array tables, non-contiguous
|
|
746
|
+
* sub-tables).
|
|
747
|
+
*/
|
|
748
|
+
export function pruneOwnedMcpServerBlock(raw, serverId, isOwned) {
|
|
749
|
+
if (hasAmbiguousMcpServerDottedDefinition(raw, serverId) ||
|
|
750
|
+
hasManagedMcpServerTableConflict(raw, serverId)) {
|
|
751
|
+
return { kind: "ambiguous", raw };
|
|
752
|
+
}
|
|
753
|
+
const lines = raw.trimEnd().split(/\r?\n/);
|
|
754
|
+
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
755
|
+
const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
|
|
756
|
+
if (start === -1)
|
|
757
|
+
return { kind: "absent", raw };
|
|
758
|
+
const end = managedSectionEnd(lines, startsInsideMultiline, start, serverId);
|
|
759
|
+
const launch = readManagedBlockLaunch(lines, startsInsideMultiline, start, end);
|
|
760
|
+
if (launch === null)
|
|
761
|
+
return { kind: "preserved", raw, command: null };
|
|
762
|
+
if (!isOwned(launch.command, launch.args)) {
|
|
763
|
+
return { kind: "preserved", raw, command: launch.command };
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
kind: "removed",
|
|
767
|
+
raw: rebuildWithoutSection(lines, start, end, dominantEol(raw)),
|
|
667
768
|
command: launch.command,
|
|
668
|
-
|
|
669
|
-
codexEnv: WEBPRESSO_MCP_SPEC.env,
|
|
670
|
-
});
|
|
769
|
+
};
|
|
671
770
|
}
|
|
672
|
-
|
|
673
|
-
|
|
771
|
+
function legacyPruneResult(outcome, path) {
|
|
772
|
+
switch (outcome.kind) {
|
|
773
|
+
case "removed":
|
|
774
|
+
return { kind: "codex-legacy-webpresso-mcp-removed", path, command: outcome.command };
|
|
775
|
+
case "preserved":
|
|
776
|
+
return { kind: "codex-legacy-webpresso-mcp-preserved", path, command: outcome.command };
|
|
777
|
+
case "ambiguous":
|
|
778
|
+
return { kind: "codex-legacy-webpresso-mcp-ambiguous", path };
|
|
779
|
+
case "absent":
|
|
780
|
+
return { kind: "codex-legacy-webpresso-mcp-absent", path };
|
|
781
|
+
}
|
|
674
782
|
}
|
|
675
|
-
|
|
783
|
+
/**
|
|
784
|
+
* Retire the legacy user-global `[mcp_servers.webpresso]` block. Runs from
|
|
785
|
+
* setup AND from the `wp codex` launch preflight — the launch path is the only
|
|
786
|
+
* one that reaches machines that never re-run setup.
|
|
787
|
+
*/
|
|
788
|
+
export function pruneLegacyCodexWebpressoMcp(input) {
|
|
676
789
|
const configPath = input.configPath ?? defaultConfigPath();
|
|
677
790
|
if (input.options.dryRun) {
|
|
678
|
-
return { kind: "codex-webpresso-mcp-skipped-dry-run", path: configPath };
|
|
791
|
+
return { kind: "codex-legacy-webpresso-mcp-skipped-dry-run", path: configPath };
|
|
679
792
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (!entryPath) {
|
|
683
|
-
const checked = resolution?.checked ?? [];
|
|
684
|
-
return { kind: "codex-webpresso-mcp-not-installed", path: configPath, checked };
|
|
793
|
+
if (!existsSync(configPath)) {
|
|
794
|
+
return { kind: "codex-legacy-webpresso-mcp-absent", path: configPath };
|
|
685
795
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
796
|
+
// Classify INSIDE the locked transaction so a concurrent writer's block is
|
|
797
|
+
// never judged from a pre-lock read (PR#767 lost-update guarantee).
|
|
798
|
+
let outcome = { kind: "absent", raw: "" };
|
|
799
|
+
updateCodexConfig(configPath, (raw) => {
|
|
800
|
+
outcome = pruneOwnedMcpServerBlock(raw, WEBPRESSO_MCP_SERVER_NAME, (command, args) => isAgentKitOwnedWpCommand(command, args, input.ownership));
|
|
801
|
+
return outcome.raw;
|
|
802
|
+
});
|
|
803
|
+
return legacyPruneResult(outcome, configPath);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Installed Codex plugin MCP manifests — the surviving delivery channel.
|
|
807
|
+
* Cheap directory probe only: no `codex plugin` subprocess belongs on the
|
|
808
|
+
* launch hot path.
|
|
809
|
+
*/
|
|
810
|
+
export function findInstalledCodexPluginMcpManifests(homeDir = process.env.HOME || homedir()) {
|
|
811
|
+
const base = join(homeDir, ".codex", "plugins", "cache");
|
|
812
|
+
const manifests = [];
|
|
813
|
+
for (const marketplace of safeReadDir(base)) {
|
|
814
|
+
const pluginDir = join(base, marketplace, "agent-kit");
|
|
815
|
+
for (const version of safeReadDir(pluginDir)) {
|
|
816
|
+
const manifest = join(pluginDir, version, ".mcp.json");
|
|
817
|
+
if (existsSync(manifest))
|
|
818
|
+
manifests.push(manifest);
|
|
819
|
+
}
|
|
690
820
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
821
|
+
return manifests;
|
|
822
|
+
}
|
|
823
|
+
function safeReadDir(dir) {
|
|
824
|
+
try {
|
|
825
|
+
const entries = readdirSync(dir);
|
|
826
|
+
if (!Array.isArray(entries))
|
|
827
|
+
return [];
|
|
828
|
+
return entries.filter((entry) => typeof entry === "string");
|
|
829
|
+
}
|
|
830
|
+
catch {
|
|
831
|
+
return [];
|
|
694
832
|
}
|
|
695
|
-
return { kind: "codex-webpresso-mcp-written", path: configPath, entryPath };
|
|
696
833
|
}
|