create-principles-disciple 1.146.9 → 1.147.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release/product-identity.json +1 -1
- package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +6 -0
- package/codex-adapter/dist/ingestion/explicit-owner-statement.js +6 -0
- package/codex-adapter/dist/pd-hook.js +5 -2
- package/codex-adapter/dist/worker/workspace-worker.d.ts +13 -1
- package/codex-adapter/dist/worker/workspace-worker.js +26 -3
- package/codex-adapter/package.json +1 -1
- package/console/dist/shared/prompt-injection-contract.d.ts +1 -1
- package/console/dist/web/assets/app.js +31 -31
- package/core/dist/prompt-builder/size-guard.d.ts.map +1 -1
- package/core/dist/prompt-builder/size-guard.js +6 -1
- package/core/dist/prompt-builder/size-guard.js.map +1 -1
- package/core/dist/prompt-builder/types.d.ts +2 -1
- package/core/dist/prompt-builder/types.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts +12 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js +12 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts +42 -11
- package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-budget-policy.js +82 -30
- package/core/dist/runtime-v2/activation/prompt-budget-policy.js.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js +17 -5
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +2 -2
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/intent/intent-friction-block.d.ts +2 -2
- package/core/dist/runtime-v2/intent/intent-friction-block.js +2 -2
- package/core/dist/runtime-v2/pain-ingress-payload.d.ts +13 -1
- package/core/dist/runtime-v2/pain-ingress-payload.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-ingress-payload.js +21 -2
- package/core/dist/runtime-v2/pain-ingress-payload.js.map +1 -1
- package/core/dist/runtime-v2/pain-ingress.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-ingress.js +3 -0
- package/core/dist/runtime-v2/pain-ingress.js.map +1 -1
- package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts +35 -0
- package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts.map +1 -0
- package/core/dist/runtime-v2/signal-collector/confidence-gate.js +48 -0
- package/core/dist/runtime-v2/signal-collector/confidence-gate.js.map +1 -0
- package/core/dist/runtime-v2/signal-collector/index.d.ts +1 -0
- package/core/dist/runtime-v2/signal-collector/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/signal-collector/index.js +1 -0
- package/core/dist/runtime-v2/signal-collector/index.js.map +1 -1
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts +4 -6
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts.map +1 -1
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js +100 -9
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js.map +1 -1
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js +8 -4
- package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
- package/core/dist/runtime-v2/types/event-types.d.ts +2 -2
- package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/event-types.js +1 -0
- package/core/dist/runtime-v2/types/event-types.js.map +1 -1
- package/core/package.json +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +52 -1
- package/dist/installer.js.map +1 -1
- package/dist/installers/mcp-registration-conflict.d.ts +35 -0
- package/dist/installers/mcp-registration-conflict.d.ts.map +1 -0
- package/dist/installers/mcp-registration-conflict.js +559 -0
- package/dist/installers/mcp-registration-conflict.js.map +1 -0
- package/dist/installers/openclaw-host-installer.d.ts.map +1 -1
- package/dist/installers/openclaw-host-installer.js +20 -0
- package/dist/installers/openclaw-host-installer.js.map +1 -1
- package/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
- package/dist/installers/workbuddy-host-installer.js +124 -2
- package/dist/installers/workbuddy-host-installer.js.map +1 -1
- package/dist/mcp-server-layout-compat.d.ts +18 -0
- package/dist/mcp-server-layout-compat.d.ts.map +1 -0
- package/dist/mcp-server-layout-compat.js +22 -0
- package/dist/mcp-server-layout-compat.js.map +1 -0
- package/host-runtime/dist/governance-signal-admission.d.ts +147 -4
- package/host-runtime/dist/governance-signal-admission.js +1065 -60
- package/host-runtime/dist/index.d.ts +2 -0
- package/host-runtime/dist/index.js +2 -0
- package/host-runtime/dist/prompt-injection-budget-observation.js +4 -2
- package/host-runtime/dist/signal-classifier.d.ts +46 -0
- package/host-runtime/dist/signal-classifier.js +167 -0
- package/host-runtime/dist/signal-stage2-cycle.d.ts +42 -0
- package/host-runtime/dist/signal-stage2-cycle.js +184 -0
- package/host-runtime/package.json +1 -1
- package/install-layout/dist/index.d.ts +15 -0
- package/install-layout/dist/index.js +10 -0
- package/install-layout/package.json +1 -1
- package/package.json +9 -7
- package/pd-cli/dist/commands/health-codex.d.ts.map +1 -1
- package/pd-cli/dist/commands/health-codex.js +12 -0
- package/pd-cli/dist/commands/health-codex.js.map +1 -1
- package/pd-cli/dist/commands/health-mcp.d.ts +74 -0
- package/pd-cli/dist/commands/health-mcp.d.ts.map +1 -0
- package/pd-cli/dist/commands/health-mcp.js +723 -0
- package/pd-cli/dist/commands/health-mcp.js.map +1 -0
- package/pd-cli/dist/commands/health.d.ts +2 -0
- package/pd-cli/dist/commands/health.d.ts.map +1 -1
- package/pd-cli/dist/commands/health.js +11 -6
- package/pd-cli/dist/commands/health.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/pd-mcp-server/dist/budget.d.ts +15 -0
- package/pd-mcp-server/dist/budget.js +19 -0
- package/pd-mcp-server/dist/deadline.d.ts +75 -0
- package/pd-mcp-server/dist/deadline.js +136 -0
- package/pd-mcp-server/dist/envelope.d.ts +52 -0
- package/pd-mcp-server/dist/envelope.js +63 -0
- package/pd-mcp-server/dist/index.d.ts +10 -0
- package/pd-mcp-server/dist/index.js +76 -0
- package/pd-mcp-server/dist/logging.d.ts +12 -0
- package/pd-mcp-server/dist/logging.js +42 -0
- package/pd-mcp-server/dist/server.d.ts +90 -0
- package/pd-mcp-server/dist/server.js +178 -0
- package/pd-mcp-server/dist/version.d.ts +13 -0
- package/pd-mcp-server/dist/version.js +13 -0
- package/pd-mcp-server/dist/workspace.d.ts +23 -0
- package/pd-mcp-server/dist/workspace.js +77 -0
- package/pd-mcp-server/package.json +49 -0
- package/plugin/dist/bundle.js +414 -412
- package/plugin/dist/core/mcp-binding.d.ts +41 -0
- package/plugin/dist/governance-audit.js +2 -2
- package/plugin/dist/index.d.ts +33 -0
- package/plugin/dist/openclaw.plugin.json +10 -1
- package/plugin/dist/utils/workspace-resolver.d.ts +17 -0
- package/plugin/openclaw.plugin.json +10 -1
- package/plugin/package.json +2 -1
- package/release-manager/dist/installer.js +52 -1
- package/release-manager/dist/installers/mcp-registration-conflict.js +559 -0
- package/release-manager/dist/installers/openclaw-host-installer.js +20 -0
- package/release-manager/dist/installers/workbuddy-host-installer.js +124 -2
- package/release-manager/dist/mcp-server-layout-compat.js +22 -0
- package/release-manager/package.json +7 -5
- package/workbuddy-adapter/dist/codec/input-decoder.js +3 -1
- package/workbuddy-adapter/dist/pd-hook.js +28 -5
- package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +6 -1
- package/workbuddy-adapter/dist/worker/workspace-worker.js +24 -3
- package/workbuddy-adapter/package.json +1 -1
- package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +2 -2
- package/workbuddy-adapter/plugin/.mcp.json +10 -0
- package/workbuddy-adapter/plugin/mcp/launcher.cjs +347 -0
|
@@ -14,7 +14,8 @@ import { t } from './i18n.js';
|
|
|
14
14
|
import { generateConfigYamlContent, ExistingConfigVerifyInfraError, validateExistingConfigYamlForPreserve, getConfigYamlPath, readEnabledChannelsFromConfigYaml, getOpenClawDir, getPdDir, getPdRuntimeDir, getInstallManifestPath, getPdRuntimeBackupsDir, getPluginExtDir, getInstalledPluginDir, getInstalledLayoutPackageDir, getPdBackupsDir, getInstalledPdCliDir, getInstalledBinDir, getInstalledConsoleDir, getHomeDir, isWindows, } from './mvp-config.js';
|
|
15
15
|
import { getHostInstallers } from './installers/index.js';
|
|
16
16
|
import { runWorkbuddyEarlyPreflight } from './installers/workbuddy-host-installer.js';
|
|
17
|
-
import { INSTALL_LAYOUT_VERSION, getWorkbuddyWorkspaceDir, installManifestHostsForPersist, mergeInstallManifestWorkspaces, parseInstallManifest } from '@principles/install-layout';
|
|
17
|
+
import { INSTALL_LAYOUT_VERSION, getInstallLayoutPaths, getWorkbuddyWorkspaceDir, installManifestHostsForPersist, mergeInstallManifestWorkspaces, parseInstallManifest } from '@principles/install-layout';
|
|
18
|
+
import { resolveMcpServerDir } from './mcp-server-layout-compat.js';
|
|
18
19
|
import { detectOpenClawMainWorkspaceDivergence } from './utils/workspace-divergence.js';
|
|
19
20
|
import { applySkillLanguageSelection } from './skill-language.js';
|
|
20
21
|
import { parseReleaseAssetIdentity, parseReleaseAssetManifest, ReleaseAssetManifestError, verifyReleaseAssetManifestAsync, verifyReleaseAssetTarget, } from './update/release-asset-manifest.js';
|
|
@@ -659,6 +660,7 @@ const NPM_DISTRIBUTED_COMPONENTS = [
|
|
|
659
660
|
['PD CLI', 'pd-cli'],
|
|
660
661
|
['Console', 'console'],
|
|
661
662
|
['Install layout', 'install-layout'],
|
|
663
|
+
['MCP server', 'pd-mcp-server'],
|
|
662
664
|
];
|
|
663
665
|
// Components whose install step demands MORE than the generic
|
|
664
666
|
// package.json+dist shape check — the per-component extra files are
|
|
@@ -812,6 +814,7 @@ const SELF_CONTAINED_COMPONENTS = [
|
|
|
812
814
|
['PD CLI', 'pd-cli'],
|
|
813
815
|
['Console', 'console'],
|
|
814
816
|
['Install layout', 'install-layout'],
|
|
817
|
+
['MCP server', 'pd-mcp-server'],
|
|
815
818
|
];
|
|
816
819
|
export async function preflightSelfContainedReleaseAsset(assetDir) {
|
|
817
820
|
const identityPath = path.join(assetDir, '_release', 'asset.json');
|
|
@@ -2190,6 +2193,38 @@ function getInstalledCodexAdapterDir() {
|
|
|
2190
2193
|
function getInstalledWorkbuddyAdapterDir() {
|
|
2191
2194
|
return path.join(getPdRuntimeDir(), 'workbuddy-adapter');
|
|
2192
2195
|
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Gate 4 (PRI-1071): the shared MCP server installs as a runtime sibling so
|
|
2198
|
+
* the three host launchers can point their PD-owned bindings at one canonical
|
|
2199
|
+
* entry (~/.pd/runtime/pd-mcp-server/dist/index.js).
|
|
2200
|
+
*/
|
|
2201
|
+
function getInstalledMcpServerDir() {
|
|
2202
|
+
return resolveMcpServerDir(getInstallLayoutPaths(getHomeDir()));
|
|
2203
|
+
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Gate 4 (PRI-1071): deploy the bundled @principles/pd-mcp-server the same
|
|
2206
|
+
* way as the codex/workbuddy adapters. Observable skip (not a failure) for
|
|
2207
|
+
* installer builds older than the component — the host launchers then fail
|
|
2208
|
+
* closed at launch with the honest "server entry missing" state (SPEC §3.5).
|
|
2209
|
+
* Link materialization happens via ensureComponentFileDependencyLinks AFTER
|
|
2210
|
+
* all siblings exist (same ordering contract as the codex adapter).
|
|
2211
|
+
*/
|
|
2212
|
+
function installBundledMcpServer(pluginDir) {
|
|
2213
|
+
const mcpServerSrc = path.join(pluginDir, 'pd-mcp-server');
|
|
2214
|
+
const mcpServerDest = getInstalledMcpServerDir();
|
|
2215
|
+
if (!existsSync(mcpServerSrc)) {
|
|
2216
|
+
logger.warn(`Bundled @principles/pd-mcp-server not found in this installer package (${mcpServerSrc}) — the pd-recall MCP tool stays unavailable in this deployment. Hook/CLI/Console installation is unaffected.`);
|
|
2217
|
+
return false;
|
|
2218
|
+
}
|
|
2219
|
+
const mcpServerPkgJson = path.join(mcpServerSrc, 'package.json');
|
|
2220
|
+
const mcpServerDistEntry = path.join(mcpServerSrc, 'dist', 'index.js');
|
|
2221
|
+
if (!existsSync(mcpServerPkgJson) || !existsSync(mcpServerDistEntry)) {
|
|
2222
|
+
throw new Error('Bundled @principles/pd-mcp-server is incomplete (missing package.json / dist/index.js). Package may be corrupted.');
|
|
2223
|
+
}
|
|
2224
|
+
rmSync(mcpServerDest, { recursive: true, force: true });
|
|
2225
|
+
cpSync(mcpServerSrc, mcpServerDest, { recursive: true });
|
|
2226
|
+
return true;
|
|
2227
|
+
}
|
|
2193
2228
|
function installBundledCore(pluginDir) {
|
|
2194
2229
|
const coreSrc = path.join(pluginDir, 'core');
|
|
2195
2230
|
const coreDest = getInstalledCoreDir();
|
|
@@ -2598,6 +2633,9 @@ async function installConsoleDependencies() {
|
|
|
2598
2633
|
const consoleDest = getInstalledConsoleDir();
|
|
2599
2634
|
await prepareComponentDependencies(consoleDest, 'Console');
|
|
2600
2635
|
}
|
|
2636
|
+
async function installMcpServerDependencies() {
|
|
2637
|
+
await prepareComponentDependencies(getInstalledMcpServerDir(), 'MCP server');
|
|
2638
|
+
}
|
|
2601
2639
|
const CONSOLE_PORT_MAX_RETRIES = 3;
|
|
2602
2640
|
/**
|
|
2603
2641
|
* P0-2 (SPEC v1.2 §5 step 6 / invariant I-4): terminate a child and WAIT for
|
|
@@ -3727,6 +3765,19 @@ transaction) {
|
|
|
3727
3765
|
// installer builds is an observable skip, not a failure.
|
|
3728
3766
|
installBundledWorkbuddyAdapter(pluginDir);
|
|
3729
3767
|
ensureWorkbuddyAdapterResolution();
|
|
3768
|
+
// Gate 4 (PRI-1071): the shared MCP server refreshes with the runtime so
|
|
3769
|
+
// the host launchers' bindings always point at the current canonical
|
|
3770
|
+
// entry. Older installer payloads skip observably (the launchers fail
|
|
3771
|
+
// closed at start with the honest missing-entry state).
|
|
3772
|
+
const mcpServerInstalled = installBundledMcpServer(pluginDir);
|
|
3773
|
+
if (mcpServerInstalled) {
|
|
3774
|
+
ensureComponentFileDependencyLinks(getInstalledMcpServerDir(), 'pd-mcp-server');
|
|
3775
|
+
// The npm-distributed payload ships only dist/ + package.json, and the
|
|
3776
|
+
// MCP server keeps @modelcontextprotocol/server + zod as registry deps
|
|
3777
|
+
// (unlike the file:-linked siblings). Without this pass the launcher
|
|
3778
|
+
// starts an entry whose imports cannot resolve.
|
|
3779
|
+
await installMcpServerDependencies();
|
|
3780
|
+
}
|
|
3730
3781
|
if (spinner)
|
|
3731
3782
|
updateProgress(spinner, stepIndex, 'Installing release-manager authority module...');
|
|
3732
3783
|
installBundledReleaseManagerPackage(pluginDir);
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three-host MCP same-name registration conflict gate — SPEC-PD-MCP-GATE4-001
|
|
3
|
+
* §7.5 ("同名冲突时停止,不覆盖用户配置") / §11.3 ("同名冲突 | 明确报告,不覆盖").
|
|
4
|
+
*
|
|
5
|
+
* The host-specific MCP configs PD shares a name with are:
|
|
6
|
+
* - Codex: ~/.codex/config.toml `[mcp_servers.<name>]` (user tier
|
|
7
|
+
* overrides the plugin tier — Gate 4-A reality check).
|
|
8
|
+
* - OpenClaw: ~/.openclaw/openclaw.json `mcp.servers.<name>` (operator
|
|
9
|
+
* tier overrides plugin-owned servers).
|
|
10
|
+
* - WorkBuddy: ~/.workbuddy/mcp.json `mcpServers.<name>` (user-level MCP
|
|
11
|
+
* config; plugin servers are configured independently).
|
|
12
|
+
*
|
|
13
|
+
* Classification is ONE closed set of outcomes (never "guess and continue"):
|
|
14
|
+
* - `absent` no same-name registration, install may proceed.
|
|
15
|
+
* - `other_servers_only` other names coexist; PD's name is free.
|
|
16
|
+
* - `pd_owned` the same name already points at PD's launcher —
|
|
17
|
+
* idempotent reuse, no duplicate registration.
|
|
18
|
+
* - conflict same name points at ANOTHER MCP server → STOP before
|
|
19
|
+
* any write; never overwrite or delete the entry.
|
|
20
|
+
* - unverifiable config/ownership cannot be proven → STOP fail-closed
|
|
21
|
+
* (rc-1/rc-3: unknown is not "PD-owned").
|
|
22
|
+
*
|
|
23
|
+
* This module is a READ-ONLY classifier: no registration manager, no second
|
|
24
|
+
* source of truth. Installers call it before any config/binding/cache write;
|
|
25
|
+
* `pd health` reports the same verdict.
|
|
26
|
+
*/
|
|
27
|
+
import * as fs from 'fs';
|
|
28
|
+
import * as os from 'os';
|
|
29
|
+
import * as path from 'path';
|
|
30
|
+
export const PD_MCP_SERVER_NAME = 'pd-recall';
|
|
31
|
+
/** Stamp carried by PD's own launcher (see mcp/launcher.cjs `PD_LAUNCHER_ID`). */
|
|
32
|
+
const PD_LAUNCHER_ID = 'pd-mcp-launcher.v1';
|
|
33
|
+
/** Second stamp: the launcher's own binding-schema constant. Filename alone proves nothing. */
|
|
34
|
+
const PD_BINDING_SCHEMA_MARKER = 'pd.mcp-binding.v1';
|
|
35
|
+
/** A launcher is a small script; refuse to slurp arbitrary large files. */
|
|
36
|
+
const MAX_LAUNCHER_BYTES = 512 * 1024;
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function getHomeDir(options) {
|
|
41
|
+
return path.resolve(options.homeDir ?? os.homedir());
|
|
42
|
+
}
|
|
43
|
+
export function resolveMcpRegistrationConfigPath(host, options = {}) {
|
|
44
|
+
const home = getHomeDir(options);
|
|
45
|
+
if (host === 'codex') {
|
|
46
|
+
const codexHome = options.codexHome ?? (process.env.CODEX_HOME && process.env.CODEX_HOME.length > 0
|
|
47
|
+
? process.env.CODEX_HOME
|
|
48
|
+
: path.join(home, '.codex'));
|
|
49
|
+
return path.join(codexHome, 'config.toml');
|
|
50
|
+
}
|
|
51
|
+
if (host === 'openclaw')
|
|
52
|
+
return path.join(home, '.openclaw', 'openclaw.json');
|
|
53
|
+
const configDir = options.workbuddyConfigDir
|
|
54
|
+
?? (process.env.WORKBUDDY_CONFIG_DIR && process.env.WORKBUDDY_CONFIG_DIR.length > 0
|
|
55
|
+
? process.env.WORKBUDDY_CONFIG_DIR
|
|
56
|
+
: path.join(home, '.workbuddy'));
|
|
57
|
+
return path.join(configDir, 'mcp.json');
|
|
58
|
+
}
|
|
59
|
+
const HOST_MARKER_PATHS = {
|
|
60
|
+
codex: ['.codex-plugin', 'plugin.json'],
|
|
61
|
+
openclaw: ['openclaw.plugin.json'],
|
|
62
|
+
workbuddy: ['.codebuddy-plugin', 'plugin.json'],
|
|
63
|
+
};
|
|
64
|
+
function isPathWithin(child, root) {
|
|
65
|
+
const relative = path.relative(root, child);
|
|
66
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
67
|
+
}
|
|
68
|
+
/** Resolve BOTH sides before checking containment; lexical paths are not trust boundaries. */
|
|
69
|
+
function isInsideRoot(child, root) {
|
|
70
|
+
try {
|
|
71
|
+
const realRoot = fs.realpathSync(root);
|
|
72
|
+
const realChild = fs.realpathSync(child);
|
|
73
|
+
return isPathWithin(realChild, realRoot);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function readPluginHostIdentity(pluginRoot, expectedHost) {
|
|
80
|
+
const realRoot = fs.realpathSync(pluginRoot);
|
|
81
|
+
const detected = [];
|
|
82
|
+
for (const host of Object.keys(HOST_MARKER_PATHS)) {
|
|
83
|
+
const markerPath = path.join(realRoot, ...HOST_MARKER_PATHS[host]);
|
|
84
|
+
let markerStat;
|
|
85
|
+
try {
|
|
86
|
+
markerStat = fs.lstatSync(markerPath);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (isRecord(error) && error.code === 'ENOENT')
|
|
90
|
+
continue;
|
|
91
|
+
return 'unverifiable';
|
|
92
|
+
}
|
|
93
|
+
// Marker files are identity evidence, not links to identity evidence.
|
|
94
|
+
if (!markerStat.isFile() || markerStat.isSymbolicLink())
|
|
95
|
+
return 'unverifiable';
|
|
96
|
+
let realMarker;
|
|
97
|
+
try {
|
|
98
|
+
realMarker = fs.realpathSync(markerPath);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return 'unverifiable';
|
|
102
|
+
}
|
|
103
|
+
if (!isPathWithin(realMarker, realRoot))
|
|
104
|
+
return 'unverifiable';
|
|
105
|
+
let markerRecord;
|
|
106
|
+
try {
|
|
107
|
+
markerRecord = JSON.parse(fs.readFileSync(realMarker, 'utf8'));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return 'unverifiable';
|
|
111
|
+
}
|
|
112
|
+
const markerKey = host === 'openclaw' ? 'id' : 'name';
|
|
113
|
+
if (!isRecord(markerRecord) || markerRecord[markerKey] !== 'principles-disciple')
|
|
114
|
+
return 'unverifiable';
|
|
115
|
+
detected.push(host);
|
|
116
|
+
}
|
|
117
|
+
if (detected.length !== 1)
|
|
118
|
+
return 'unverifiable';
|
|
119
|
+
return detected[0] === expectedHost ? 'match' : 'other';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Prove launcher identity by BOTH content and host ownership. The launcher
|
|
123
|
+
* copies are intentionally byte-identical across hosts, so their containing
|
|
124
|
+
* plugin root's unique manifest marker is part of the identity contract.
|
|
125
|
+
*/
|
|
126
|
+
function readLauncherIdentity(file, expectedHost, trustedRoots = []) {
|
|
127
|
+
let real;
|
|
128
|
+
try {
|
|
129
|
+
real = fs.realpathSync(file);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return 'unverifiable';
|
|
133
|
+
}
|
|
134
|
+
if (path.basename(real).toLowerCase() !== 'launcher.cjs'
|
|
135
|
+
|| path.basename(path.dirname(real)).toLowerCase() !== 'mcp')
|
|
136
|
+
return 'other';
|
|
137
|
+
// If the configured path is lexically under a known payload root, its real
|
|
138
|
+
// target must remain under that root. This blocks file/directory symlink and
|
|
139
|
+
// junction escapes before any ownership shortcut can be considered.
|
|
140
|
+
const lexicalFile = path.resolve(file);
|
|
141
|
+
for (const root of trustedRoots) {
|
|
142
|
+
const lexicalRoot = path.resolve(root);
|
|
143
|
+
if (isPathWithin(lexicalFile, lexicalRoot) && !isInsideRoot(real, root))
|
|
144
|
+
return 'unverifiable';
|
|
145
|
+
}
|
|
146
|
+
// Read through ONE open descriptor: a stat-then-read pair leaves a window in
|
|
147
|
+
// which the path can be swapped (CodeQL js/file-system-race). fstat+read on
|
|
148
|
+
// the same handle validates and reads the same file instance.
|
|
149
|
+
let fd;
|
|
150
|
+
try {
|
|
151
|
+
fd = fs.openSync(real, 'r');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return 'unverifiable';
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const stat = fs.fstatSync(fd);
|
|
158
|
+
if (!stat.isFile())
|
|
159
|
+
return 'unverifiable';
|
|
160
|
+
if (stat.size > MAX_LAUNCHER_BYTES)
|
|
161
|
+
return 'other';
|
|
162
|
+
const buffer = Buffer.alloc(stat.size);
|
|
163
|
+
fs.readSync(fd, buffer, 0, stat.size, 0);
|
|
164
|
+
const content = buffer.toString('utf8');
|
|
165
|
+
if (!content.includes(PD_LAUNCHER_ID) || !content.includes(PD_BINDING_SCHEMA_MARKER))
|
|
166
|
+
return 'other';
|
|
167
|
+
const pluginRoot = path.dirname(path.dirname(real));
|
|
168
|
+
const hostIdentity = readPluginHostIdentity(pluginRoot, expectedHost);
|
|
169
|
+
return hostIdentity === 'match' ? 'pd' : hostIdentity;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return 'unverifiable';
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
fs.closeSync(fd);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Is this arg shaped like a PD launcher argument at all (`.../mcp/launcher.cjs`)? */
|
|
179
|
+
function launcherArgPath(arg) {
|
|
180
|
+
const normalized = arg.replace(/\\/g, '/');
|
|
181
|
+
if (path.basename(normalized) !== 'launcher.cjs')
|
|
182
|
+
return undefined;
|
|
183
|
+
if (path.basename(path.dirname(normalized)) !== 'mcp')
|
|
184
|
+
return undefined;
|
|
185
|
+
return arg;
|
|
186
|
+
}
|
|
187
|
+
function entryOwnership(entry, expectedHost, options = {}) {
|
|
188
|
+
if (!isRecord(entry))
|
|
189
|
+
return 'unverifiable';
|
|
190
|
+
const command = typeof entry.command === 'string' ? entry.command : undefined;
|
|
191
|
+
const args = Array.isArray(entry.args) ? entry.args.filter((arg) => typeof arg === 'string') : undefined;
|
|
192
|
+
const url = typeof entry.url === 'string' ? entry.url : undefined;
|
|
193
|
+
if (command === undefined) {
|
|
194
|
+
// No command: a URL-based server is definitely someone else's; anything
|
|
195
|
+
// else cannot be attributed.
|
|
196
|
+
return url !== undefined ? 'other' : 'unverifiable';
|
|
197
|
+
}
|
|
198
|
+
const commandBase = path.basename(command).toLowerCase();
|
|
199
|
+
if (commandBase !== 'node' && commandBase !== 'node.exe' && commandBase !== path.basename(process.execPath).toLowerCase()) {
|
|
200
|
+
return 'other';
|
|
201
|
+
}
|
|
202
|
+
if (args === undefined)
|
|
203
|
+
return 'unverifiable';
|
|
204
|
+
const candidate = args.map(launcherArgPath).find((arg) => arg !== undefined);
|
|
205
|
+
if (candidate === undefined)
|
|
206
|
+
return 'other';
|
|
207
|
+
// Relative args and unresolved host placeholders (${CODEBUDDY_PLUGIN_ROOT})
|
|
208
|
+
// depend on a cwd/variable the config does not fix: ownership is unprovable.
|
|
209
|
+
if (candidate.includes('${') || !path.isAbsolute(candidate))
|
|
210
|
+
return 'unverifiable';
|
|
211
|
+
const resolved = path.resolve(candidate);
|
|
212
|
+
const identity = readLauncherIdentity(resolved, expectedHost, options.pdLauncherRoots ?? []);
|
|
213
|
+
if (identity === 'pd')
|
|
214
|
+
return 'pd_owned';
|
|
215
|
+
return identity === 'other' ? 'other' : 'unverifiable';
|
|
216
|
+
}
|
|
217
|
+
function classifyEntryPresence(input) {
|
|
218
|
+
const { present, entry, host, configPath, ownershipOptions } = input;
|
|
219
|
+
if (!present)
|
|
220
|
+
return { ok: true, host, configPath, state: 'absent' };
|
|
221
|
+
const ownership = entryOwnership(entry, host, ownershipOptions);
|
|
222
|
+
if (ownership === 'pd_owned')
|
|
223
|
+
return { ok: true, host, configPath, state: 'pd_owned' };
|
|
224
|
+
if (ownership === 'unverifiable') {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
host,
|
|
228
|
+
configPath,
|
|
229
|
+
kind: 'unverifiable',
|
|
230
|
+
reason: `mcp_registration_unverifiable: ${configPath} declares mcpServers["${PD_MCP_SERVER_NAME}"] but its launcher identity cannot be proven (relative path, unresolved placeholder, or unreadable file). No PD write was performed.`,
|
|
231
|
+
nextAction: `Inspect ${configPath} manually: point the "${PD_MCP_SERVER_NAME}" entry at PD's absolute mcp/launcher.cjs path, remove it if it is not PD's, then re-run the installer. PD never guesses ownership from a file name.`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
host,
|
|
237
|
+
configPath,
|
|
238
|
+
kind: 'conflict',
|
|
239
|
+
reason: `mcp_same_name_conflict: ${configPath} registers "${PD_MCP_SERVER_NAME}" with a launcher not owned by ${host} (third-party or another PD host). The installer stopped before any write; the existing entry was left byte-identical.`,
|
|
240
|
+
nextAction: `Point "${PD_MCP_SERVER_NAME}" at ${host}'s own absolute mcp/launcher.cjs path, or remove/rename the foreign entry in ${configPath}, then re-run the installer. PD never reuses another host's launcher.`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// ─── JSON hosts (OpenClaw / WorkBuddy) ──────────────────────────────────────
|
|
244
|
+
function readJsonObject(configPath) {
|
|
245
|
+
let raw;
|
|
246
|
+
try {
|
|
247
|
+
raw = fs.readFileSync(configPath, 'utf8');
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (isRecord(error) && error.code === 'ENOENT')
|
|
251
|
+
return { ok: true, value: undefined };
|
|
252
|
+
return { ok: false, reason: `unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(raw);
|
|
256
|
+
if (!isRecord(parsed))
|
|
257
|
+
return { ok: false, reason: 'root is not a JSON object' };
|
|
258
|
+
return { ok: true, value: parsed };
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return { ok: false, reason: `not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function classifyJsonHost(host, configPath, options = {}) {
|
|
265
|
+
const file = readJsonObject(configPath);
|
|
266
|
+
if (!file.ok) {
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
host,
|
|
270
|
+
configPath,
|
|
271
|
+
kind: 'unverifiable',
|
|
272
|
+
reason: `mcp_registration_unverifiable: ${configPath} ${file.reason}. No PD write was performed.`,
|
|
273
|
+
nextAction: `Fix or remove ${configPath} manually so the MCP server list is readable, then re-run the installer. PD never merges into a config it cannot parse.`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (file.value === undefined)
|
|
277
|
+
return { ok: true, host, configPath, state: 'absent' };
|
|
278
|
+
const root = host === 'openclaw' ? file.value.mcp : file.value;
|
|
279
|
+
if (root === undefined)
|
|
280
|
+
return { ok: true, host, configPath, state: 'absent' };
|
|
281
|
+
if (!isRecord(root)) {
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
host,
|
|
285
|
+
configPath,
|
|
286
|
+
kind: 'unverifiable',
|
|
287
|
+
reason: `mcp_registration_unverifiable: ${configPath} ${host === 'openclaw' ? 'mcp' : 'root'} is not an object. No PD write was performed.`,
|
|
288
|
+
nextAction: `Repair ${configPath} manually, then re-run the installer.`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const servers = host === 'openclaw' ? root.servers : root.mcpServers;
|
|
292
|
+
if (servers === undefined)
|
|
293
|
+
return { ok: true, host, configPath, state: 'absent' };
|
|
294
|
+
if (!isRecord(servers)) {
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
host,
|
|
298
|
+
configPath,
|
|
299
|
+
kind: 'unverifiable',
|
|
300
|
+
reason: `mcp_registration_unverifiable: ${configPath} ${host === 'openclaw' ? 'mcp.servers' : 'mcpServers'} is not an object. No PD write was performed.`,
|
|
301
|
+
nextAction: `Repair ${configPath} manually, then re-run the installer.`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const present = Object.hasOwn(servers, PD_MCP_SERVER_NAME);
|
|
305
|
+
if (present)
|
|
306
|
+
return classifyEntryPresence({ present: true, entry: servers[PD_MCP_SERVER_NAME], host, configPath, ownershipOptions: options });
|
|
307
|
+
return {
|
|
308
|
+
ok: true,
|
|
309
|
+
host,
|
|
310
|
+
configPath,
|
|
311
|
+
state: Object.keys(servers).length === 0 ? 'absent' : 'other_servers_only',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
export function classifyOpenClawConfigObject(config, configPath, options = {}) {
|
|
315
|
+
const { mcp } = config;
|
|
316
|
+
if (mcp === undefined)
|
|
317
|
+
return { ok: true, host: 'openclaw', configPath, state: 'absent' };
|
|
318
|
+
if (!isRecord(mcp)) {
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
host: 'openclaw',
|
|
322
|
+
configPath,
|
|
323
|
+
kind: 'unverifiable',
|
|
324
|
+
reason: `mcp_registration_unverifiable: ${configPath} mcp is not an object. No PD write was performed.`,
|
|
325
|
+
nextAction: `Repair ${configPath} manually, then re-run the installer.`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const { servers } = mcp;
|
|
329
|
+
if (servers === undefined)
|
|
330
|
+
return { ok: true, host: 'openclaw', configPath, state: 'absent' };
|
|
331
|
+
if (!isRecord(servers)) {
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
host: 'openclaw',
|
|
335
|
+
configPath,
|
|
336
|
+
kind: 'unverifiable',
|
|
337
|
+
reason: `mcp_registration_unverifiable: ${configPath} mcp.servers is not an object. No PD write was performed.`,
|
|
338
|
+
nextAction: `Repair ${configPath} manually, then re-run the installer.`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (Object.hasOwn(servers, PD_MCP_SERVER_NAME)) {
|
|
342
|
+
return classifyEntryPresence({ present: true, entry: servers[PD_MCP_SERVER_NAME], host: 'openclaw', configPath, ownershipOptions: options });
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
ok: true,
|
|
346
|
+
host: 'openclaw',
|
|
347
|
+
configPath,
|
|
348
|
+
state: Object.keys(servers).length === 0 ? 'absent' : 'other_servers_only',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function parseTomlScalarString(raw) {
|
|
352
|
+
const value = raw.trim();
|
|
353
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
354
|
+
try {
|
|
355
|
+
const parsed = JSON.parse(value);
|
|
356
|
+
return typeof parsed === 'string' ? parsed : undefined;
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (value.startsWith("'") && value.endsWith("'"))
|
|
363
|
+
return value.slice(1, -1);
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
function parseTomlStringArray(raw) {
|
|
367
|
+
const value = raw.trim();
|
|
368
|
+
if (!value.startsWith('[') || !value.endsWith(']'))
|
|
369
|
+
return undefined;
|
|
370
|
+
const inner = value.slice(1, -1).trim();
|
|
371
|
+
if (inner.length === 0)
|
|
372
|
+
return [];
|
|
373
|
+
const parts = inner.split(',').map((part) => part.trim()).filter((part) => part.length > 0);
|
|
374
|
+
const out = [];
|
|
375
|
+
for (const part of parts) {
|
|
376
|
+
const parsed = parseTomlScalarString(part);
|
|
377
|
+
if (parsed === undefined)
|
|
378
|
+
return undefined;
|
|
379
|
+
out.push(parsed);
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
function scanCodexToml(raw) {
|
|
384
|
+
const entry = { opaque: false };
|
|
385
|
+
let found = false;
|
|
386
|
+
let otherServers = false;
|
|
387
|
+
let table;
|
|
388
|
+
let multilineArgs = undefined;
|
|
389
|
+
const header = (line) => {
|
|
390
|
+
const captured = /^\s*\[\[?([^\]]+)\]\]?\s*(?:#.*)?$/.exec(line)?.[1];
|
|
391
|
+
return captured === undefined ? undefined : captured.trim();
|
|
392
|
+
};
|
|
393
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
394
|
+
if (multilineArgs !== undefined) {
|
|
395
|
+
const close = line.indexOf(']');
|
|
396
|
+
const segment = close === -1 ? line : line.slice(0, close);
|
|
397
|
+
const parsed = parseTomlScalarString(segment.replace(/,$/, '').trim());
|
|
398
|
+
if (parsed !== undefined)
|
|
399
|
+
multilineArgs.push(parsed);
|
|
400
|
+
if (close !== -1) {
|
|
401
|
+
entry.args = multilineArgs;
|
|
402
|
+
multilineArgs = undefined;
|
|
403
|
+
}
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const trimmed = line.trim();
|
|
407
|
+
if (trimmed.length === 0 || trimmed.startsWith('#'))
|
|
408
|
+
continue;
|
|
409
|
+
const named = header(line);
|
|
410
|
+
if (named !== undefined) {
|
|
411
|
+
if (/^mcp_servers\./.test(named)) {
|
|
412
|
+
const parts = named.split('.').map((part) => part.trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1'));
|
|
413
|
+
const [, serverName] = parts;
|
|
414
|
+
if (serverName === PD_MCP_SERVER_NAME) {
|
|
415
|
+
found = true;
|
|
416
|
+
table = parts.length === 2 ? 'server' : 'child';
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
// Another server is fine; a nested table of pd-recall is ignored.
|
|
420
|
+
if (parts.length >= 2)
|
|
421
|
+
otherServers = true;
|
|
422
|
+
table = undefined;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
table = undefined;
|
|
427
|
+
}
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (/^\s*mcp_servers\s*=/.test(line)) {
|
|
431
|
+
// Top-level inline table / array form — not modelled.
|
|
432
|
+
found = true;
|
|
433
|
+
entry.opaque = true;
|
|
434
|
+
table = undefined;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (table === undefined) {
|
|
438
|
+
// Dotted keys: mcp_servers."pd-recall".command = ...
|
|
439
|
+
const dotted = /^\s*mcp_servers\.(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+))\.(command|args|url)\s*=\s*(.*)$/.exec(line);
|
|
440
|
+
if (dotted) {
|
|
441
|
+
const name = dotted[1] ?? dotted[2] ?? dotted[3];
|
|
442
|
+
if (name === PD_MCP_SERVER_NAME) {
|
|
443
|
+
found = true;
|
|
444
|
+
const [, , , , key, value] = dotted;
|
|
445
|
+
if (key === undefined || value === undefined) {
|
|
446
|
+
entry.opaque = true;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (key === 'command')
|
|
450
|
+
entry.command = parseTomlScalarString(value);
|
|
451
|
+
else if (key === 'url')
|
|
452
|
+
entry.url = parseTomlScalarString(value);
|
|
453
|
+
else {
|
|
454
|
+
const parsed = parseTomlStringArray(value);
|
|
455
|
+
if (parsed !== undefined)
|
|
456
|
+
entry.args = parsed;
|
|
457
|
+
else if (value.trim().startsWith('[')) {
|
|
458
|
+
multilineArgs = [];
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
entry.opaque = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (table !== 'server')
|
|
470
|
+
continue;
|
|
471
|
+
const pair = /^\s*(command|args|url)\s*=\s*(.*)$/.exec(line);
|
|
472
|
+
if (!pair)
|
|
473
|
+
continue;
|
|
474
|
+
const [, key, value] = pair;
|
|
475
|
+
if (key === undefined || value === undefined)
|
|
476
|
+
continue;
|
|
477
|
+
if (key === 'command') {
|
|
478
|
+
entry.command = parseTomlScalarString(value);
|
|
479
|
+
if (entry.command === undefined)
|
|
480
|
+
entry.opaque = true;
|
|
481
|
+
}
|
|
482
|
+
else if (key === 'url') {
|
|
483
|
+
entry.url = parseTomlScalarString(value);
|
|
484
|
+
if (entry.url === undefined)
|
|
485
|
+
entry.opaque = true;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
const parsed = parseTomlStringArray(value);
|
|
489
|
+
if (parsed !== undefined)
|
|
490
|
+
entry.args = parsed;
|
|
491
|
+
else if (value.trim().startsWith('['))
|
|
492
|
+
multilineArgs = [];
|
|
493
|
+
else
|
|
494
|
+
entry.opaque = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return found ? { entry, otherServers } : { otherServers };
|
|
498
|
+
}
|
|
499
|
+
function classifyCodex(configPath, options = {}) {
|
|
500
|
+
let raw;
|
|
501
|
+
try {
|
|
502
|
+
raw = fs.readFileSync(configPath, 'utf8');
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
if (isRecord(error) && error.code === 'ENOENT')
|
|
506
|
+
return { ok: true, host: 'codex', configPath, state: 'absent' };
|
|
507
|
+
return {
|
|
508
|
+
ok: false,
|
|
509
|
+
host: 'codex',
|
|
510
|
+
configPath,
|
|
511
|
+
kind: 'unverifiable',
|
|
512
|
+
reason: `mcp_registration_unverifiable: ${configPath} is unreadable (${error instanceof Error ? error.message : String(error)}). No PD write was performed.`,
|
|
513
|
+
nextAction: `Fix ${configPath} read access, then re-run the installer. PD never guesses another server's ownership.`,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
const scan = scanCodexToml(raw);
|
|
517
|
+
if (scan.entry === undefined) {
|
|
518
|
+
return { ok: true, host: 'codex', configPath, state: scan.otherServers ? 'other_servers_only' : 'absent' };
|
|
519
|
+
}
|
|
520
|
+
const { entry } = scan;
|
|
521
|
+
if (entry.opaque) {
|
|
522
|
+
return {
|
|
523
|
+
ok: false,
|
|
524
|
+
host: 'codex',
|
|
525
|
+
configPath,
|
|
526
|
+
kind: 'unverifiable',
|
|
527
|
+
reason: `mcp_registration_unverifiable: ${configPath} uses an mcp_servers construct PD cannot parse, so the "${PD_MCP_SERVER_NAME}" ownership is unknown. No PD write was performed.`,
|
|
528
|
+
nextAction: `Inspect ${configPath} manually (PD only understands [mcp_servers.<name>] tables and dotted mcp_servers.<name>.* keys), then re-run the installer.`,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
const ownership = entryOwnership({ command: entry.command, args: entry.args, url: entry.url }, 'codex', options);
|
|
532
|
+
if (ownership === 'pd_owned')
|
|
533
|
+
return { ok: true, host: 'codex', configPath, state: 'pd_owned' };
|
|
534
|
+
if (ownership === 'unverifiable') {
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
host: 'codex',
|
|
538
|
+
configPath,
|
|
539
|
+
kind: 'unverifiable',
|
|
540
|
+
reason: `mcp_registration_unverifiable: ${configPath} declares [mcp_servers.${PD_MCP_SERVER_NAME}] without a verifiable command/args. No PD write was performed.`,
|
|
541
|
+
nextAction: `Inspect ${configPath} manually and remove/repair the "${PD_MCP_SERVER_NAME}" entry, then re-run the installer.`,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
ok: false,
|
|
546
|
+
host: 'codex',
|
|
547
|
+
configPath,
|
|
548
|
+
kind: 'conflict',
|
|
549
|
+
reason: `mcp_same_name_conflict: ${configPath} registers "${PD_MCP_SERVER_NAME}" with a launcher not owned by Codex (third-party or another PD host; user tier overrides the plugin tier). The installer stopped before any write; the existing entry was left byte-identical.`,
|
|
550
|
+
nextAction: `Point [mcp_servers.${PD_MCP_SERVER_NAME}] at the Codex plugin's own absolute mcp/launcher.cjs path, or remove/rename the foreign entry in ${configPath}, then re-run $pd-control setup. PD never reuses another host's launcher.`,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
export function checkMcpRegistrationConflict(host, options = {}) {
|
|
554
|
+
const configPath = resolveMcpRegistrationConfigPath(host, options);
|
|
555
|
+
if (host === 'codex')
|
|
556
|
+
return classifyCodex(configPath, options);
|
|
557
|
+
return classifyJsonHost(host, configPath, options);
|
|
558
|
+
}
|
|
559
|
+
//# sourceMappingURL=mcp-registration-conflict.js.map
|