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.
Files changed (140) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +6 -0
  3. package/codex-adapter/dist/ingestion/explicit-owner-statement.js +6 -0
  4. package/codex-adapter/dist/pd-hook.js +5 -2
  5. package/codex-adapter/dist/worker/workspace-worker.d.ts +13 -1
  6. package/codex-adapter/dist/worker/workspace-worker.js +26 -3
  7. package/codex-adapter/package.json +1 -1
  8. package/console/dist/shared/prompt-injection-contract.d.ts +1 -1
  9. package/console/dist/web/assets/app.js +31 -31
  10. package/core/dist/prompt-builder/size-guard.d.ts.map +1 -1
  11. package/core/dist/prompt-builder/size-guard.js +6 -1
  12. package/core/dist/prompt-builder/size-guard.js.map +1 -1
  13. package/core/dist/prompt-builder/types.d.ts +2 -1
  14. package/core/dist/prompt-builder/types.d.ts.map +1 -1
  15. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts +12 -1
  16. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts.map +1 -1
  17. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js +12 -1
  18. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js.map +1 -1
  19. package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts +42 -11
  20. package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts.map +1 -1
  21. package/core/dist/runtime-v2/activation/prompt-budget-policy.js +82 -30
  22. package/core/dist/runtime-v2/activation/prompt-budget-policy.js.map +1 -1
  23. package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts.map +1 -1
  24. package/core/dist/runtime-v2/adapter/pi-ai-catalog.js +17 -5
  25. package/core/dist/runtime-v2/adapter/pi-ai-catalog.js.map +1 -1
  26. package/core/dist/runtime-v2/index.d.ts +2 -2
  27. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  28. package/core/dist/runtime-v2/index.js +1 -1
  29. package/core/dist/runtime-v2/index.js.map +1 -1
  30. package/core/dist/runtime-v2/intent/intent-friction-block.d.ts +2 -2
  31. package/core/dist/runtime-v2/intent/intent-friction-block.js +2 -2
  32. package/core/dist/runtime-v2/pain-ingress-payload.d.ts +13 -1
  33. package/core/dist/runtime-v2/pain-ingress-payload.d.ts.map +1 -1
  34. package/core/dist/runtime-v2/pain-ingress-payload.js +21 -2
  35. package/core/dist/runtime-v2/pain-ingress-payload.js.map +1 -1
  36. package/core/dist/runtime-v2/pain-ingress.d.ts.map +1 -1
  37. package/core/dist/runtime-v2/pain-ingress.js +3 -0
  38. package/core/dist/runtime-v2/pain-ingress.js.map +1 -1
  39. package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts +35 -0
  40. package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts.map +1 -0
  41. package/core/dist/runtime-v2/signal-collector/confidence-gate.js +48 -0
  42. package/core/dist/runtime-v2/signal-collector/confidence-gate.js.map +1 -0
  43. package/core/dist/runtime-v2/signal-collector/index.d.ts +1 -0
  44. package/core/dist/runtime-v2/signal-collector/index.d.ts.map +1 -1
  45. package/core/dist/runtime-v2/signal-collector/index.js +1 -0
  46. package/core/dist/runtime-v2/signal-collector/index.js.map +1 -1
  47. package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts +4 -6
  48. package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts.map +1 -1
  49. package/core/dist/runtime-v2/signal-collector/keyword-stage.js +100 -9
  50. package/core/dist/runtime-v2/signal-collector/keyword-stage.js.map +1 -1
  51. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.d.ts.map +1 -1
  52. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js +8 -4
  53. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
  54. package/core/dist/runtime-v2/types/event-types.d.ts +2 -2
  55. package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
  56. package/core/dist/runtime-v2/types/event-types.js +1 -0
  57. package/core/dist/runtime-v2/types/event-types.js.map +1 -1
  58. package/core/package.json +1 -1
  59. package/dist/installer.d.ts.map +1 -1
  60. package/dist/installer.js +52 -1
  61. package/dist/installer.js.map +1 -1
  62. package/dist/installers/mcp-registration-conflict.d.ts +35 -0
  63. package/dist/installers/mcp-registration-conflict.d.ts.map +1 -0
  64. package/dist/installers/mcp-registration-conflict.js +559 -0
  65. package/dist/installers/mcp-registration-conflict.js.map +1 -0
  66. package/dist/installers/openclaw-host-installer.d.ts.map +1 -1
  67. package/dist/installers/openclaw-host-installer.js +20 -0
  68. package/dist/installers/openclaw-host-installer.js.map +1 -1
  69. package/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
  70. package/dist/installers/workbuddy-host-installer.js +124 -2
  71. package/dist/installers/workbuddy-host-installer.js.map +1 -1
  72. package/dist/mcp-server-layout-compat.d.ts +18 -0
  73. package/dist/mcp-server-layout-compat.d.ts.map +1 -0
  74. package/dist/mcp-server-layout-compat.js +22 -0
  75. package/dist/mcp-server-layout-compat.js.map +1 -0
  76. package/host-runtime/dist/governance-signal-admission.d.ts +147 -4
  77. package/host-runtime/dist/governance-signal-admission.js +1065 -60
  78. package/host-runtime/dist/index.d.ts +2 -0
  79. package/host-runtime/dist/index.js +2 -0
  80. package/host-runtime/dist/prompt-injection-budget-observation.js +4 -2
  81. package/host-runtime/dist/signal-classifier.d.ts +46 -0
  82. package/host-runtime/dist/signal-classifier.js +167 -0
  83. package/host-runtime/dist/signal-stage2-cycle.d.ts +42 -0
  84. package/host-runtime/dist/signal-stage2-cycle.js +184 -0
  85. package/host-runtime/package.json +1 -1
  86. package/install-layout/dist/index.d.ts +15 -0
  87. package/install-layout/dist/index.js +10 -0
  88. package/install-layout/package.json +1 -1
  89. package/package.json +9 -7
  90. package/pd-cli/dist/commands/health-codex.d.ts.map +1 -1
  91. package/pd-cli/dist/commands/health-codex.js +12 -0
  92. package/pd-cli/dist/commands/health-codex.js.map +1 -1
  93. package/pd-cli/dist/commands/health-mcp.d.ts +74 -0
  94. package/pd-cli/dist/commands/health-mcp.d.ts.map +1 -0
  95. package/pd-cli/dist/commands/health-mcp.js +723 -0
  96. package/pd-cli/dist/commands/health-mcp.js.map +1 -0
  97. package/pd-cli/dist/commands/health.d.ts +2 -0
  98. package/pd-cli/dist/commands/health.d.ts.map +1 -1
  99. package/pd-cli/dist/commands/health.js +11 -6
  100. package/pd-cli/dist/commands/health.js.map +1 -1
  101. package/pd-cli/package.json +1 -1
  102. package/pd-mcp-server/dist/budget.d.ts +15 -0
  103. package/pd-mcp-server/dist/budget.js +19 -0
  104. package/pd-mcp-server/dist/deadline.d.ts +75 -0
  105. package/pd-mcp-server/dist/deadline.js +136 -0
  106. package/pd-mcp-server/dist/envelope.d.ts +52 -0
  107. package/pd-mcp-server/dist/envelope.js +63 -0
  108. package/pd-mcp-server/dist/index.d.ts +10 -0
  109. package/pd-mcp-server/dist/index.js +76 -0
  110. package/pd-mcp-server/dist/logging.d.ts +12 -0
  111. package/pd-mcp-server/dist/logging.js +42 -0
  112. package/pd-mcp-server/dist/server.d.ts +90 -0
  113. package/pd-mcp-server/dist/server.js +178 -0
  114. package/pd-mcp-server/dist/version.d.ts +13 -0
  115. package/pd-mcp-server/dist/version.js +13 -0
  116. package/pd-mcp-server/dist/workspace.d.ts +23 -0
  117. package/pd-mcp-server/dist/workspace.js +77 -0
  118. package/pd-mcp-server/package.json +49 -0
  119. package/plugin/dist/bundle.js +414 -412
  120. package/plugin/dist/core/mcp-binding.d.ts +41 -0
  121. package/plugin/dist/governance-audit.js +2 -2
  122. package/plugin/dist/index.d.ts +33 -0
  123. package/plugin/dist/openclaw.plugin.json +10 -1
  124. package/plugin/dist/utils/workspace-resolver.d.ts +17 -0
  125. package/plugin/openclaw.plugin.json +10 -1
  126. package/plugin/package.json +2 -1
  127. package/release-manager/dist/installer.js +52 -1
  128. package/release-manager/dist/installers/mcp-registration-conflict.js +559 -0
  129. package/release-manager/dist/installers/openclaw-host-installer.js +20 -0
  130. package/release-manager/dist/installers/workbuddy-host-installer.js +124 -2
  131. package/release-manager/dist/mcp-server-layout-compat.js +22 -0
  132. package/release-manager/package.json +7 -5
  133. package/workbuddy-adapter/dist/codec/input-decoder.js +3 -1
  134. package/workbuddy-adapter/dist/pd-hook.js +28 -5
  135. package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +6 -1
  136. package/workbuddy-adapter/dist/worker/workspace-worker.js +24 -3
  137. package/workbuddy-adapter/package.json +1 -1
  138. package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +2 -2
  139. package/workbuddy-adapter/plugin/.mcp.json +10 -0
  140. 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