@getmarrow/install 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/installer.js +372 -68
package/README.md
CHANGED
|
@@ -99,7 +99,11 @@ npx @getmarrow/install controller stop
|
|
|
99
99
|
|
|
100
100
|
Persistent controller lifecycle is currently Linux-only. On macOS or Windows, activation still writes supported configuration and verifies one server-side install self-test without certifying that hooks continuously ran; run `npx @getmarrow/install sidecar` under an owner-managed service and pass `--no-controller`. The controller does not silently upgrade packages, change governance policy, rotate credentials, or modify unrelated project configuration.
|
|
101
101
|
|
|
102
|
-
## What's New in v0.1.
|
|
102
|
+
## What's New in v0.1.55
|
|
103
|
+
|
|
104
|
+
v0.1.55 pins sealed MCP candidate `3.9.79` from source `11f00049043d0aba90704ecbf69f32d2278a4573` for ordinary setup, generated launch configuration, and native hooks. Doctor, update, and repair retain exact-version resolution and never propagate a newer local version unless official registry metadata verifies it; offline operation preserves unverified-ahead owner surfaces and uses the sealed candidate for new managed targets.
|
|
105
|
+
|
|
106
|
+
## Previous: v0.1.54
|
|
103
107
|
|
|
104
108
|
v0.1.54 pins sealed MCP candidate `3.9.77` from source `1e782d8ba6bbb54bfaa322f0300565c7176f1969` and makes ordinary setup use the primary tool surface without writing a profile variable. Explicit `core` and `full` selections remain preserved, invalid values fail closed with a bounded repair, and human/JSON self-test output distinguishes configured/effective profile, expected visibility, actual post-reload visibility, and backend-projected entitlement state. Backend projections are status evidence only and always report `authorizes_calls: false`.
|
|
105
109
|
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -10,9 +10,9 @@ const { evidence: localControlEvidence } = require('./control-state');
|
|
|
10
10
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
11
11
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
12
12
|
const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
|
|
13
|
-
const MCP_ADAPTER_VERSION = '3.9.
|
|
14
|
-
const MCP_ADAPTER_SOURCE_SHA = '
|
|
15
|
-
const MCP_ADAPTER_INTEGRITY = 'sha512-
|
|
13
|
+
const MCP_ADAPTER_VERSION = '3.9.79';
|
|
14
|
+
const MCP_ADAPTER_SOURCE_SHA = '11f00049043d0aba90704ecbf69f32d2278a4573';
|
|
15
|
+
const MCP_ADAPTER_INTEGRITY = 'sha512-LWfBWGot2PnluRbS5Hm3WA2DJCIkf2qVJfthGcCaZx8AoRXFKeg1pUO3rMVFqrOIWOB4nYZw6UgSL+7hDY1wuA==';
|
|
16
16
|
const SDK_ADAPTER_VERSION = '3.7.62';
|
|
17
17
|
const SDK_ADAPTER_INTEGRITY = 'sha512-n1i6Be09TpAQ9BPNRKY7aCvA2iSUPpJfw8djw2MELwpNbBCtKiZ29Jji77BK/6EFLUpSIcTW/Gmdf/ccf0JRYQ==';
|
|
18
18
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
@@ -31,6 +31,184 @@ const ADAPTER_PROVENANCE = Object.freeze({
|
|
|
31
31
|
integrity: SDK_ADAPTER_INTEGRITY,
|
|
32
32
|
}),
|
|
33
33
|
});
|
|
34
|
+
const MCP_REGISTRY_LATEST_URL = 'https://registry.npmjs.org/%40getmarrow%2Fmcp/latest';
|
|
35
|
+
const MCP_REGISTRY_VERIFICATION_COMMAND = 'npm view @getmarrow/mcp@latest name version dist.integrity dist.tarball --json --registry=https://registry.npmjs.org';
|
|
36
|
+
const MCP_STABLE_VERSION_RE = /^(\d{1,6})\.(\d{1,6})\.(\d{1,9})$/;
|
|
37
|
+
const SHA512_INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
|
|
38
|
+
const VERIFIED_MCP_EXECUTABLE_TARGET = Symbol('verified_mcp_executable_target');
|
|
39
|
+
|
|
40
|
+
function validSha512Integrity(value) {
|
|
41
|
+
if (!SHA512_INTEGRITY_RE.test(value)) return false;
|
|
42
|
+
const encoded = value.slice('sha512-'.length);
|
|
43
|
+
try {
|
|
44
|
+
const digest = Buffer.from(encoded, 'base64');
|
|
45
|
+
return digest.length === 64 && digest.toString('base64') === encoded;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parsedStableMcpVersion(value) {
|
|
52
|
+
const match = String(value || '').match(MCP_STABLE_VERSION_RE);
|
|
53
|
+
if (!match) return null;
|
|
54
|
+
const parts = match.slice(1).map(Number);
|
|
55
|
+
return parts.every(Number.isSafeInteger) ? parts : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function compareMcpVersions(left, right) {
|
|
59
|
+
const leftParts = parsedStableMcpVersion(left);
|
|
60
|
+
const rightParts = parsedStableMcpVersion(right);
|
|
61
|
+
if (!leftParts || !rightParts) return null;
|
|
62
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
63
|
+
if (leftParts[index] !== rightParts[index]) return leftParts[index] - rightParts[index];
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function compatibleMcpTargetVersion(value) {
|
|
69
|
+
const candidate = parsedStableMcpVersion(value);
|
|
70
|
+
const sealed = parsedStableMcpVersion(MCP_ADAPTER_VERSION);
|
|
71
|
+
return Boolean(candidate && sealed
|
|
72
|
+
&& candidate[0] === sealed[0]
|
|
73
|
+
&& candidate[1] === sealed[1]
|
|
74
|
+
&& compareMcpVersions(value, MCP_ADAPTER_VERSION) >= 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mcpVersionsInText(value) {
|
|
78
|
+
return [...String(value || '').matchAll(/@getmarrow\/mcp@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/g)]
|
|
79
|
+
.map((match) => match[1]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function unverifiedAheadMcpVersions(versions, targetVersion = MCP_ADAPTER_VERSION) {
|
|
83
|
+
if (!compatibleMcpTargetVersion(targetVersion)) return [];
|
|
84
|
+
return [...new Set((Array.isArray(versions) ? versions : [])
|
|
85
|
+
.filter((version) => compatibleMcpTargetVersion(version)
|
|
86
|
+
&& compareMcpVersions(version, targetVersion) > 0))].sort((left, right) => compareMcpVersions(left, right));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mcpRegistryVerificationAction(versions) {
|
|
90
|
+
const ahead = [...new Set(Array.isArray(versions) ? versions : [])].sort((left, right) => compareMcpVersions(left, right));
|
|
91
|
+
const targets = ahead.map((version) => `@getmarrow/mcp@${version}`).join(', ');
|
|
92
|
+
return `Run ${MCP_REGISTRY_VERIFICATION_COMMAND} with official npm registry access, then rerun npx -y @getmarrow/install@latest doctor --self-test. Automatic repair is suppressed${targets ? ` for ${targets}` : ''}; preserve each existing surface until registry metadata verifies it or the owner chooses the sealed or verified version.`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function verifiedMcpRegistryMetadata(value) {
|
|
96
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
97
|
+
const version = typeof value.version === 'string' ? value.version : '';
|
|
98
|
+
const integrity = typeof value.dist?.integrity === 'string' ? value.dist.integrity : '';
|
|
99
|
+
const tarball = typeof value.dist?.tarball === 'string' ? value.dist.tarball : '';
|
|
100
|
+
const expectedTarball = `https://registry.npmjs.org/@getmarrow/mcp/-/mcp-${version}.tgz`;
|
|
101
|
+
if (value.name !== '@getmarrow/mcp'
|
|
102
|
+
|| !compatibleMcpTargetVersion(version)
|
|
103
|
+
|| !validSha512Integrity(integrity)
|
|
104
|
+
|| tarball !== expectedTarball) return null;
|
|
105
|
+
return { version, integrity, tarball };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveMcpTargetVersion(options = {}) {
|
|
109
|
+
const registry = verifiedMcpRegistryMetadata(options.registryMetadata);
|
|
110
|
+
if (registry) {
|
|
111
|
+
const target = {
|
|
112
|
+
version: registry.version,
|
|
113
|
+
source: 'verified_npm_registry',
|
|
114
|
+
integrity: registry.integrity,
|
|
115
|
+
source_sha: null,
|
|
116
|
+
};
|
|
117
|
+
Object.defineProperty(target, VERIFIED_MCP_EXECUTABLE_TARGET, { value: true });
|
|
118
|
+
return target;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
version: MCP_ADAPTER_VERSION,
|
|
122
|
+
source: 'sealed_installer',
|
|
123
|
+
integrity: MCP_ADAPTER_INTEGRITY,
|
|
124
|
+
source_sha: MCP_ADAPTER_SOURCE_SHA,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function executableMcpTarget(options = {}) {
|
|
129
|
+
const target = options.mcpTarget;
|
|
130
|
+
if (target?.[VERIFIED_MCP_EXECUTABLE_TARGET] === true
|
|
131
|
+
&& target.source === 'verified_npm_registry'
|
|
132
|
+
&& compatibleMcpTargetVersion(target.version)
|
|
133
|
+
&& validSha512Integrity(target.integrity)
|
|
134
|
+
&& target.source_sha === null) return target;
|
|
135
|
+
return resolveMcpTargetVersion();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function expectedMcpInspectionVersion(options = {}) {
|
|
139
|
+
if (options.expectedVersion === MCP_ADAPTER_VERSION) return MCP_ADAPTER_VERSION;
|
|
140
|
+
return executableMcpTarget({ mcpTarget: options.expectedTarget }).version;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function readMcpRegistryMetadata(options = {}) {
|
|
144
|
+
if (Object.prototype.hasOwnProperty.call(options, 'mcpRegistryMetadata')) {
|
|
145
|
+
return options.mcpRegistryMetadata;
|
|
146
|
+
}
|
|
147
|
+
if (options.resolveMcpRegistry !== true) return null;
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timer = setTimeout(() => controller.abort(), 2500);
|
|
150
|
+
try {
|
|
151
|
+
const fetcher = typeof options.registryFetch === 'function' ? options.registryFetch : fetch;
|
|
152
|
+
const response = await fetcher(MCP_REGISTRY_LATEST_URL, {
|
|
153
|
+
headers: { accept: 'application/json' },
|
|
154
|
+
redirect: 'error',
|
|
155
|
+
signal: controller.signal,
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) return null;
|
|
158
|
+
return await response.json();
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
} finally {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function adapterProvenanceForMcpTarget(target) {
|
|
167
|
+
if (!target || target.version === MCP_ADAPTER_VERSION) return ADAPTER_PROVENANCE;
|
|
168
|
+
return {
|
|
169
|
+
mcp: {
|
|
170
|
+
package: '@getmarrow/mcp',
|
|
171
|
+
version: target.version,
|
|
172
|
+
source_sha: target.source_sha,
|
|
173
|
+
integrity: target.integrity,
|
|
174
|
+
integrity_state: target.source === 'verified_npm_registry'
|
|
175
|
+
? 'verified_npm_registry_metadata'
|
|
176
|
+
: 'exact_current_configuration',
|
|
177
|
+
},
|
|
178
|
+
sdk: ADAPTER_PROVENANCE.sdk,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function retargetMcpPackageSpec(value, version) {
|
|
183
|
+
const target = compatibleMcpTargetVersion(version) ? version : MCP_ADAPTER_VERSION;
|
|
184
|
+
return String(value).split(MCP_PACKAGE_SPEC).join(`@getmarrow/mcp@${target}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function retargetMcpDowngradeRecommendation(value, targetVersion) {
|
|
188
|
+
if (typeof value !== 'string' || !compatibleMcpTargetVersion(targetVersion)) return value;
|
|
189
|
+
return value.replace(/@getmarrow\/mcp@(\d+\.\d+\.\d+)/g, (match, version) => (
|
|
190
|
+
parsedStableMcpVersion(version) && version !== targetVersion
|
|
191
|
+
? `@getmarrow/mcp@${targetVersion}`
|
|
192
|
+
: match
|
|
193
|
+
));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function alignMcpRecommendationVersions(value, targetVersion, key = '', verificationAction = null) {
|
|
197
|
+
if (Array.isArray(value)) {
|
|
198
|
+
return value.map((entry) => alignMcpRecommendationVersions(entry, targetVersion, key, verificationAction));
|
|
199
|
+
}
|
|
200
|
+
if (!value || typeof value !== 'object') {
|
|
201
|
+
return /(?:command|fix|instruction|next_action|notice)$/i.test(key)
|
|
202
|
+
? verificationAction && typeof value === 'string' && /@getmarrow\/mcp@\d+\.\d+\.\d+/.test(value)
|
|
203
|
+
? verificationAction
|
|
204
|
+
: retargetMcpDowngradeRecommendation(value, targetVersion)
|
|
205
|
+
: value;
|
|
206
|
+
}
|
|
207
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
208
|
+
entryKey,
|
|
209
|
+
alignMcpRecommendationVersions(entryValue, targetVersion, entryKey, verificationAction),
|
|
210
|
+
]));
|
|
211
|
+
}
|
|
34
212
|
const MCP_CONTEXT_HOOK_COMMAND = `npx -y --package=${MCP_PACKAGE_SPEC} marrow-mcp context-hook`;
|
|
35
213
|
const MCP_PRE_ACTION_HOOK_COMMAND = `npx -y --package=${MCP_PACKAGE_SPEC} marrow-mcp pre-action-hook`;
|
|
36
214
|
const MCP_ACTION_RESULT_HOOK_COMMAND = `npx -y --package=${MCP_PACKAGE_SPEC} marrow-mcp hook`;
|
|
@@ -366,24 +544,37 @@ function inspectMcpProcesses(options = {}) {
|
|
|
366
544
|
.filter(isMcpProcessCommand)
|
|
367
545
|
.map((command) => explicitMcpVersion(command) || packageMcpVersion(command) || 'unknown');
|
|
368
546
|
const versions = [...new Set(active.filter((version) => version !== 'unknown'))].sort();
|
|
547
|
+
const expectedVersion = expectedMcpInspectionVersion(options);
|
|
369
548
|
const unknownVersionProcesses = active.filter((version) => version === 'unknown').length;
|
|
370
|
-
const
|
|
549
|
+
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(versions, expectedVersion);
|
|
550
|
+
const staleVersions = versions.filter((version) => version !== expectedVersion
|
|
551
|
+
&& !aheadUnverifiedVersions.includes(version));
|
|
371
552
|
const mixedVersions = versions.length > 1 || (versions.length > 0 && unknownVersionProcesses > 0);
|
|
372
553
|
const stale = staleVersions.length > 0;
|
|
373
|
-
const
|
|
374
|
-
const
|
|
554
|
+
const aheadUnverified = aheadUnverifiedVersions.length > 0;
|
|
555
|
+
const needsRepair = stale || mixedVersions || unknownVersionProcesses > 0 || aheadUnverified;
|
|
556
|
+
const automaticRepairSuppressed = aheadUnverified;
|
|
557
|
+
const repairCommand = `npx -y --package=@getmarrow/mcp@${expectedVersion} marrow-mcp setup`;
|
|
375
558
|
return {
|
|
376
559
|
available: process.platform === 'linux' || Array.isArray(options.commands),
|
|
377
|
-
expected_version:
|
|
560
|
+
expected_version: expectedVersion,
|
|
378
561
|
active_processes: active.length,
|
|
379
562
|
active_versions: versions,
|
|
380
563
|
unknown_version_processes: unknownVersionProcesses,
|
|
381
564
|
stale_versions: staleVersions,
|
|
565
|
+
ahead_unverified: aheadUnverified,
|
|
566
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
382
567
|
mixed_versions: mixedVersions,
|
|
383
568
|
healthy: !needsRepair,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
569
|
+
automatic_repair_suppressed: automaticRepairSuppressed,
|
|
570
|
+
registry_verification_required: aheadUnverified,
|
|
571
|
+
exact_fix: automaticRepairSuppressed
|
|
572
|
+
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
573
|
+
: needsRepair ? repairCommand : null,
|
|
574
|
+
restart_required: needsRepair && !automaticRepairSuppressed,
|
|
575
|
+
restart_instruction: needsRepair && !automaticRepairSuppressed
|
|
576
|
+
? 'Restart every owning harness to replace its active Marrow MCP process.'
|
|
577
|
+
: null,
|
|
387
578
|
verification_command: needsRepair ? 'npx -y @getmarrow/install@latest doctor --self-test' : null,
|
|
388
579
|
};
|
|
389
580
|
}
|
|
@@ -422,20 +613,33 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
422
613
|
}
|
|
423
614
|
}
|
|
424
615
|
const configuredVersions = [...new Set(versions)].sort();
|
|
425
|
-
const
|
|
616
|
+
const expectedVersion = expectedMcpInspectionVersion(options);
|
|
617
|
+
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(configuredVersions, expectedVersion);
|
|
618
|
+
const staleVersions = configuredVersions.filter((version) => version !== expectedVersion
|
|
619
|
+
&& !aheadUnverifiedVersions.includes(version));
|
|
426
620
|
const mixedVersions = configuredVersions.length > 1
|
|
427
621
|
|| (configuredVersions.length > 0 && unknownVersionConfigurations > 0);
|
|
428
|
-
const
|
|
622
|
+
const aheadUnverified = aheadUnverifiedVersions.length > 0;
|
|
623
|
+
const healthy = staleVersions.length === 0 && !mixedVersions
|
|
624
|
+
&& unknownVersionConfigurations === 0 && !aheadUnverified;
|
|
429
625
|
return {
|
|
430
|
-
expected_version:
|
|
626
|
+
expected_version: expectedVersion,
|
|
431
627
|
files_checked: filesChecked,
|
|
432
628
|
configurations_found: configurationsFound,
|
|
433
629
|
configured_versions: configuredVersions,
|
|
434
630
|
unknown_version_configurations: unknownVersionConfigurations,
|
|
435
631
|
stale_versions: staleVersions,
|
|
632
|
+
ahead_unverified: aheadUnverified,
|
|
633
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
436
634
|
mixed_versions: mixedVersions,
|
|
437
635
|
healthy,
|
|
438
|
-
|
|
636
|
+
automatic_repair_suppressed: aheadUnverified,
|
|
637
|
+
registry_verification_required: aheadUnverified,
|
|
638
|
+
exact_fix: healthy
|
|
639
|
+
? null
|
|
640
|
+
: aheadUnverified
|
|
641
|
+
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
642
|
+
: `Run npx -y --package=@getmarrow/mcp@${expectedVersion} marrow-mcp setup in each owning workspace, update its MCP launch to npx -y --package=@getmarrow/mcp@${expectedVersion} marrow-mcp, then restart that harness.`,
|
|
439
643
|
verification_command: healthy ? null : 'npx -y @getmarrow/install@latest doctor --self-test',
|
|
440
644
|
};
|
|
441
645
|
}
|
|
@@ -575,6 +779,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
575
779
|
if (options.activate && options.dryRun) {
|
|
576
780
|
throw new Error('activate cannot be combined with --dry-run; use --dry-run without activate to preview changes');
|
|
577
781
|
}
|
|
782
|
+
options.resolveMcpRegistry = Boolean(options.doctor || options.repair || options.update);
|
|
578
783
|
|
|
579
784
|
return options;
|
|
580
785
|
}
|
|
@@ -1603,8 +1808,12 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1603
1808
|
const expectedHooks = capabilityLevel === 'sdk_passive_runtime'
|
|
1604
1809
|
? ['pre_action', 'action_result', 'outcome_closure']
|
|
1605
1810
|
: [...registry.automatic];
|
|
1811
|
+
const mcpTargetVersion = compatibleMcpTargetVersion(plan.mcp_target_version)
|
|
1812
|
+
? plan.mcp_target_version
|
|
1813
|
+
: MCP_ADAPTER_VERSION;
|
|
1814
|
+
const targetCommand = (command) => retargetMcpPackageSpec(command, mcpTargetVersion);
|
|
1606
1815
|
const adapterVersion = capabilityLevel === 'native_hooks' || capabilityLevel === 'mcp'
|
|
1607
|
-
?
|
|
1816
|
+
? mcpTargetVersion
|
|
1608
1817
|
: capabilityLevel === 'sdk_passive_runtime'
|
|
1609
1818
|
? SDK_ADAPTER_VERSION
|
|
1610
1819
|
: INSTALLER_ADAPTER_VERSION;
|
|
@@ -1616,71 +1825,71 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1616
1825
|
const geminiSettings = safeJsonObject(detection.paths.geminiSettings);
|
|
1617
1826
|
const grokSettings = safeJsonObject(detection.paths.grokHooks);
|
|
1618
1827
|
if (client === 'codex') {
|
|
1619
|
-
if (exactHookConfigured(codexSettings, 'UserPromptSubmit', CODEX_CONTEXT_HOOK_COMMAND)) observedHooks.push('prompt');
|
|
1620
|
-
if (exactHookConfigured(codexSettings, 'PreToolUse', CODEX_PRE_ACTION_HOOK_COMMAND, CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1621
|
-
if (exactHookConfigured(codexSettings, 'PostToolUse', CODEX_ACTION_RESULT_HOOK_COMMAND, CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1622
|
-
if (exactHookConfigured(codexSettings, 'SessionEnd', CODEX_SESSION_END_HOOK_COMMAND)) observedHooks.push('session_end');
|
|
1828
|
+
if (exactHookConfigured(codexSettings, 'UserPromptSubmit', targetCommand(CODEX_CONTEXT_HOOK_COMMAND))) observedHooks.push('prompt');
|
|
1829
|
+
if (exactHookConfigured(codexSettings, 'PreToolUse', targetCommand(CODEX_PRE_ACTION_HOOK_COMMAND), CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1830
|
+
if (exactHookConfigured(codexSettings, 'PostToolUse', targetCommand(CODEX_ACTION_RESULT_HOOK_COMMAND), CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1831
|
+
if (exactHookConfigured(codexSettings, 'SessionEnd', targetCommand(CODEX_SESSION_END_HOOK_COMMAND))) observedHooks.push('session_end');
|
|
1623
1832
|
} else if (client === 'cursor' || client === 'composer') {
|
|
1624
|
-
if (exactCursorHookConfigured(cursorSettings, 'preToolUse', CURSOR_PRE_ACTION_HOOK_COMMAND, CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1833
|
+
if (exactCursorHookConfigured(cursorSettings, 'preToolUse', targetCommand(CURSOR_PRE_ACTION_HOOK_COMMAND), CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1625
1834
|
timeout: CODEX_HOOK_TIMEOUT_SECONDS, failClosed: true, async: false,
|
|
1626
1835
|
})) observedHooks.push('pre_action');
|
|
1627
|
-
if (exactCursorHookConfigured(cursorSettings, 'postToolUse', CURSOR_ACTION_RESULT_HOOK_COMMAND, CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1836
|
+
if (exactCursorHookConfigured(cursorSettings, 'postToolUse', targetCommand(CURSOR_ACTION_RESULT_HOOK_COMMAND), CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1628
1837
|
timeout: CODEX_HOOK_TIMEOUT_SECONDS,
|
|
1629
|
-
}) && exactCursorHookConfigured(cursorSettings, 'postToolUseFailure', CURSOR_ACTION_RESULT_HOOK_COMMAND, CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1838
|
+
}) && exactCursorHookConfigured(cursorSettings, 'postToolUseFailure', targetCommand(CURSOR_ACTION_RESULT_HOOK_COMMAND), CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1630
1839
|
timeout: CODEX_HOOK_TIMEOUT_SECONDS,
|
|
1631
1840
|
})) observedHooks.push('action_result');
|
|
1632
|
-
if (exactCursorHookConfigured(cursorSettings, 'stop', CURSOR_SESSION_END_HOOK_COMMAND, undefined, {
|
|
1841
|
+
if (exactCursorHookConfigured(cursorSettings, 'stop', targetCommand(CURSOR_SESSION_END_HOOK_COMMAND), undefined, {
|
|
1633
1842
|
timeout: CODEX_SESSION_TIMEOUT_SECONDS,
|
|
1634
1843
|
})) observedHooks.push('outcome_closure');
|
|
1635
1844
|
} else if (client === 'cline') {
|
|
1636
1845
|
for (const hook of clineHookContract(detection)) {
|
|
1637
|
-
if (exactExecutableFile(hook.path, hook.content)) observedHooks.push(hook.stage);
|
|
1846
|
+
if (exactExecutableFile(hook.path, targetCommand(hook.content))) observedHooks.push(hook.stage);
|
|
1638
1847
|
}
|
|
1639
1848
|
} else if (client === 'windsurf') {
|
|
1640
1849
|
if (WINDSURF_PRE_EVENTS.every((event) => exactWindsurfHookConfigured(
|
|
1641
|
-
windsurfSettings, event, WINDSURF_PRE_ACTION_HOOK_COMMAND,
|
|
1850
|
+
windsurfSettings, event, targetCommand(WINDSURF_PRE_ACTION_HOOK_COMMAND),
|
|
1642
1851
|
))) observedHooks.push('pre_action');
|
|
1643
1852
|
if (WINDSURF_POST_EVENTS.every((event) => exactWindsurfHookConfigured(
|
|
1644
|
-
windsurfSettings, event, WINDSURF_ACTION_RESULT_HOOK_COMMAND,
|
|
1853
|
+
windsurfSettings, event, targetCommand(WINDSURF_ACTION_RESULT_HOOK_COMMAND),
|
|
1645
1854
|
))) observedHooks.push('action_result');
|
|
1646
1855
|
if (exactWindsurfHookConfigured(
|
|
1647
|
-
windsurfSettings, 'post_cascade_response', WINDSURF_SESSION_END_HOOK_COMMAND,
|
|
1856
|
+
windsurfSettings, 'post_cascade_response', targetCommand(WINDSURF_SESSION_END_HOOK_COMMAND),
|
|
1648
1857
|
)) observedHooks.push('response_closeout');
|
|
1649
1858
|
} else if (client === 'gemini' && !geminiHooksExplicitlyDisabled(geminiSettings)) {
|
|
1650
1859
|
if (exactGeminiHookConfigured(
|
|
1651
|
-
geminiSettings, 'BeforeTool', 'marrow-before-tool', GEMINI_PRE_ACTION_HOOK_COMMAND,
|
|
1860
|
+
geminiSettings, 'BeforeTool', 'marrow-before-tool', targetCommand(GEMINI_PRE_ACTION_HOOK_COMMAND),
|
|
1652
1861
|
GEMINI_NATIVE_HOOK_MATCHER, GEMINI_HOOK_TIMEOUT_MS,
|
|
1653
1862
|
)) observedHooks.push('pre_action');
|
|
1654
1863
|
if (exactGeminiHookConfigured(
|
|
1655
|
-
geminiSettings, 'AfterTool', 'marrow-after-tool', GEMINI_ACTION_RESULT_HOOK_COMMAND,
|
|
1864
|
+
geminiSettings, 'AfterTool', 'marrow-after-tool', targetCommand(GEMINI_ACTION_RESULT_HOOK_COMMAND),
|
|
1656
1865
|
GEMINI_NATIVE_HOOK_MATCHER, GEMINI_HOOK_TIMEOUT_MS,
|
|
1657
1866
|
)) observedHooks.push('action_result');
|
|
1658
1867
|
if (exactGeminiHookConfigured(
|
|
1659
|
-
geminiSettings, 'AfterAgent', 'marrow-after-agent', GEMINI_SESSION_END_HOOK_COMMAND,
|
|
1868
|
+
geminiSettings, 'AfterAgent', 'marrow-after-agent', targetCommand(GEMINI_SESSION_END_HOOK_COMMAND),
|
|
1660
1869
|
undefined, GEMINI_CLOSEOUT_TIMEOUT_MS,
|
|
1661
1870
|
)) observedHooks.push('turn_closeout');
|
|
1662
1871
|
} else if (client === 'grok') {
|
|
1663
1872
|
if (exactGrokHookConfigured(
|
|
1664
|
-
grokSettings, 'PreToolUse', GROK_PRE_ACTION_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 7,
|
|
1873
|
+
grokSettings, 'PreToolUse', targetCommand(GROK_PRE_ACTION_HOOK_COMMAND), GROK_NATIVE_HOOK_MATCHER, 7,
|
|
1665
1874
|
)) observedHooks.push('pre_action');
|
|
1666
1875
|
if (exactGrokHookConfigured(
|
|
1667
|
-
grokSettings, 'PostToolUse', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1876
|
+
grokSettings, 'PostToolUse', targetCommand(GROK_ACTION_RESULT_HOOK_COMMAND), GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1668
1877
|
) && exactGrokHookConfigured(
|
|
1669
|
-
grokSettings, 'PostToolUseFailure', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1878
|
+
grokSettings, 'PostToolUseFailure', targetCommand(GROK_ACTION_RESULT_HOOK_COMMAND), GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1670
1879
|
)) observedHooks.push('action_result');
|
|
1671
1880
|
if (exactGrokHookConfigured(
|
|
1672
|
-
grokSettings, 'Stop', GROK_SESSION_END_HOOK_COMMAND, undefined, 3,
|
|
1881
|
+
grokSettings, 'Stop', targetCommand(GROK_SESSION_END_HOOK_COMMAND), undefined, 3,
|
|
1673
1882
|
) && !grokHasDuplicateSessionEnd(grokSettings)) observedHooks.push('turn_closeout');
|
|
1674
1883
|
} else {
|
|
1675
1884
|
if (capabilityLevel === 'native_hooks'
|
|
1676
|
-
&& exactHookConfigured(claudeSettings, 'UserPromptSubmit', MCP_CONTEXT_HOOK_COMMAND)) observedHooks.push('prompt');
|
|
1885
|
+
&& exactHookConfigured(claudeSettings, 'UserPromptSubmit', targetCommand(MCP_CONTEXT_HOOK_COMMAND))) observedHooks.push('prompt');
|
|
1677
1886
|
if (capabilityLevel === 'native_hooks'
|
|
1678
|
-
&& exactHookConfigured(claudeSettings, 'PreToolUse', MCP_PRE_ACTION_HOOK_COMMAND, NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1887
|
+
&& exactHookConfigured(claudeSettings, 'PreToolUse', targetCommand(MCP_PRE_ACTION_HOOK_COMMAND), NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1679
1888
|
if (capabilityLevel === 'native_hooks'
|
|
1680
|
-
&& exactHookConfigured(claudeSettings, 'PostToolUse', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER)
|
|
1681
|
-
&& exactHookConfigured(claudeSettings, 'PostToolUseFailure', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1889
|
+
&& exactHookConfigured(claudeSettings, 'PostToolUse', targetCommand(MCP_ACTION_RESULT_HOOK_COMMAND), NATIVE_HOOK_MATCHER)
|
|
1890
|
+
&& exactHookConfigured(claudeSettings, 'PostToolUseFailure', targetCommand(MCP_ACTION_RESULT_HOOK_COMMAND), NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1682
1891
|
if (capabilityLevel === 'native_hooks'
|
|
1683
|
-
&& exactHookConfigured(claudeSettings, 'Stop', MCP_SESSION_END_HOOK_COMMAND)) observedHooks.push('session_end');
|
|
1892
|
+
&& exactHookConfigured(claudeSettings, 'Stop', targetCommand(MCP_SESSION_END_HOOK_COMMAND))) observedHooks.push('session_end');
|
|
1684
1893
|
}
|
|
1685
1894
|
const passiveRuntime = safeRead(detection.paths.passiveRuntime);
|
|
1686
1895
|
if (capabilityLevel === 'sdk_passive_runtime'
|
|
@@ -1696,7 +1905,7 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1696
1905
|
if (capabilityLevel === 'mcp' && mcpConfigs.some((config) => (
|
|
1697
1906
|
config?.mcpServers?.marrow?.command === 'npx'
|
|
1698
1907
|
&& Array.isArray(config.mcpServers.marrow.args)
|
|
1699
|
-
&& config.mcpServers.marrow.args.join(' ') === `-y --package
|
|
1908
|
+
&& config.mcpServers.marrow.args.join(' ') === `-y --package=@getmarrow/mcp@${mcpTargetVersion} marrow-mcp`
|
|
1700
1909
|
))) observedHooks.push('mcp_tool_calls');
|
|
1701
1910
|
const fingerprintMaterial = changes
|
|
1702
1911
|
.filter((change) => change.applied || change.already_present)
|
|
@@ -1737,7 +1946,7 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1737
1946
|
: client === 'gemini' && geminiHooksExplicitlyDisabled(geminiSettings)
|
|
1738
1947
|
? 'Hooks are explicitly disabled. After owner review, run /hooks enable-all, open /hooks panel, review and approve the project hook fingerprints, then restart Gemini CLI.'
|
|
1739
1948
|
: client === 'grok'
|
|
1740
|
-
? `Run npx -y --package
|
|
1949
|
+
? `Run npx -y --package=@getmarrow/mcp@${mcpTargetVersion} marrow-mcp setup, restart Grok, then inspect /hooks and confirm the global hooks are enabled.`
|
|
1741
1950
|
: 'npx @getmarrow/install --repair';
|
|
1742
1951
|
return {
|
|
1743
1952
|
adapter_version: adapterVersion,
|
|
@@ -2026,6 +2235,8 @@ function buildPlan(detection, options) {
|
|
|
2026
2235
|
const client = options.client || detectedClient(detection);
|
|
2027
2236
|
const agentId = String(options.agentId || '').trim() || stableAgentId(detection.root, client);
|
|
2028
2237
|
const baseUrl = String(options.baseUrl || DEFAULT_BASE_URL).trim() || DEFAULT_BASE_URL;
|
|
2238
|
+
const mcpTargetVersion = executableMcpTarget(options).version;
|
|
2239
|
+
const retarget = (value) => retargetMcpPackageSpec(value, mcpTargetVersion);
|
|
2029
2240
|
const mode = options.mode === 'auto'
|
|
2030
2241
|
? detection.node ? 'both' : 'mcp'
|
|
2031
2242
|
: options.mode;
|
|
@@ -2053,7 +2264,7 @@ function buildPlan(detection, options) {
|
|
|
2053
2264
|
type: 'json-transform',
|
|
2054
2265
|
path: detection.paths.claudeSettings,
|
|
2055
2266
|
label: 'Claude Code MCP passive hooks',
|
|
2056
|
-
transform: upsertClaudeHooks,
|
|
2267
|
+
transform: (filePath) => retarget(upsertClaudeHooks(filePath)),
|
|
2057
2268
|
});
|
|
2058
2269
|
}
|
|
2059
2270
|
if (detection.codex) {
|
|
@@ -2061,7 +2272,7 @@ function buildPlan(detection, options) {
|
|
|
2061
2272
|
type: 'json-transform',
|
|
2062
2273
|
path: detection.paths.codexHooks,
|
|
2063
2274
|
label: 'Codex native hooks',
|
|
2064
|
-
transform: upsertCodexHooks,
|
|
2275
|
+
transform: (filePath) => retarget(upsertCodexHooks(filePath)),
|
|
2065
2276
|
});
|
|
2066
2277
|
}
|
|
2067
2278
|
if (detection.cline) {
|
|
@@ -2070,7 +2281,7 @@ function buildPlan(detection, options) {
|
|
|
2070
2281
|
type: 'owned-executable',
|
|
2071
2282
|
path: hook.path,
|
|
2072
2283
|
label: hook.label,
|
|
2073
|
-
content: hook.content,
|
|
2284
|
+
content: retarget(hook.content),
|
|
2074
2285
|
mode: 0o755,
|
|
2075
2286
|
conflict_fix: 'Move or remove the existing owner-managed Cline hook after owner review, then run npx @getmarrow/install --repair.',
|
|
2076
2287
|
});
|
|
@@ -2081,7 +2292,7 @@ function buildPlan(detection, options) {
|
|
|
2081
2292
|
type: 'json-transform',
|
|
2082
2293
|
path: detection.paths.windsurfHooks,
|
|
2083
2294
|
label: 'Windsurf native hooks',
|
|
2084
|
-
transform: upsertWindsurfHooks,
|
|
2295
|
+
transform: (filePath) => retarget(upsertWindsurfHooks(filePath)),
|
|
2085
2296
|
});
|
|
2086
2297
|
}
|
|
2087
2298
|
if (detection.gemini) {
|
|
@@ -2089,27 +2300,27 @@ function buildPlan(detection, options) {
|
|
|
2089
2300
|
type: 'json-transform',
|
|
2090
2301
|
path: detection.paths.geminiSettings,
|
|
2091
2302
|
label: 'Gemini CLI native hooks',
|
|
2092
|
-
transform: upsertGeminiHooks,
|
|
2303
|
+
transform: (filePath) => retarget(upsertGeminiHooks(filePath)),
|
|
2093
2304
|
});
|
|
2094
2305
|
}
|
|
2095
2306
|
writes.push({
|
|
2096
2307
|
type: 'json-transform',
|
|
2097
2308
|
path: detection.paths.mcpJson,
|
|
2098
2309
|
label: 'Project MCP server config',
|
|
2099
|
-
transform: (filePath) => upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile }),
|
|
2310
|
+
transform: (filePath) => retarget(upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile })),
|
|
2100
2311
|
});
|
|
2101
2312
|
if (detection.cursor) {
|
|
2102
2313
|
writes.push({
|
|
2103
2314
|
type: 'json-transform',
|
|
2104
2315
|
path: detection.paths.cursorHooks,
|
|
2105
2316
|
label: 'Cursor native hooks',
|
|
2106
|
-
transform: upsertCursorHooks,
|
|
2317
|
+
transform: (filePath) => retarget(upsertCursorHooks(filePath)),
|
|
2107
2318
|
});
|
|
2108
2319
|
writes.push({
|
|
2109
2320
|
type: 'json-transform',
|
|
2110
2321
|
path: detection.paths.cursorMcp,
|
|
2111
2322
|
label: 'Cursor MCP server config',
|
|
2112
|
-
transform: (filePath) => upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile }),
|
|
2323
|
+
transform: (filePath) => retarget(upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile })),
|
|
2113
2324
|
});
|
|
2114
2325
|
}
|
|
2115
2326
|
}
|
|
@@ -2119,7 +2330,7 @@ function buildPlan(detection, options) {
|
|
|
2119
2330
|
type: 'md-block',
|
|
2120
2331
|
path: detection.paths.agentsMd,
|
|
2121
2332
|
label: 'Agent instructions',
|
|
2122
|
-
block: passiveInstructions(),
|
|
2333
|
+
block: retarget(passiveInstructions()),
|
|
2123
2334
|
});
|
|
2124
2335
|
}
|
|
2125
2336
|
|
|
@@ -2128,11 +2339,11 @@ function buildPlan(detection, options) {
|
|
|
2128
2339
|
type: 'file',
|
|
2129
2340
|
path: detection.paths.cursorRules,
|
|
2130
2341
|
label: 'Cursor Marrow rule',
|
|
2131
|
-
content: passiveInstructions().replace(/<!--[^>]+-->/g, '').trim() + '\n',
|
|
2342
|
+
content: retarget(passiveInstructions()).replace(/<!--[^>]+-->/g, '').trim() + '\n',
|
|
2132
2343
|
});
|
|
2133
2344
|
}
|
|
2134
2345
|
|
|
2135
|
-
return { mode, root: detection.root, writes };
|
|
2346
|
+
return { mode, root: detection.root, writes, mcp_target_version: mcpTargetVersion };
|
|
2136
2347
|
}
|
|
2137
2348
|
|
|
2138
2349
|
function assertContainedManagedTarget(root, targetPath) {
|
|
@@ -2198,9 +2409,16 @@ function applyPlan(plan, options) {
|
|
|
2198
2409
|
const prepared = plan.writes.map((write) => {
|
|
2199
2410
|
const fileExists = exists(write.path);
|
|
2200
2411
|
const before = safeRead(write.path);
|
|
2412
|
+
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(
|
|
2413
|
+
mcpVersionsInText(before),
|
|
2414
|
+
plan.mcp_target_version,
|
|
2415
|
+
);
|
|
2416
|
+
const automaticRepairSuppressed = aheadUnverifiedVersions.length > 0;
|
|
2201
2417
|
let after;
|
|
2202
2418
|
let hookConflict = false;
|
|
2203
|
-
if (
|
|
2419
|
+
if (automaticRepairSuppressed) {
|
|
2420
|
+
after = before;
|
|
2421
|
+
} else if (write.type === 'file') {
|
|
2204
2422
|
if (write.overwrite === false && before) {
|
|
2205
2423
|
after = before;
|
|
2206
2424
|
} else {
|
|
@@ -2223,21 +2441,43 @@ function applyPlan(plan, options) {
|
|
|
2223
2441
|
|
|
2224
2442
|
const beforeMode = fileExists ? fs.lstatSync(write.path).mode & 0o777 : null;
|
|
2225
2443
|
const modeChanged = !hookConflict && typeof write.mode === 'number' && beforeMode !== write.mode;
|
|
2226
|
-
return {
|
|
2444
|
+
return {
|
|
2445
|
+
write,
|
|
2446
|
+
before,
|
|
2447
|
+
after,
|
|
2448
|
+
hookConflict,
|
|
2449
|
+
modeChanged,
|
|
2450
|
+
automaticRepairSuppressed,
|
|
2451
|
+
aheadUnverifiedVersions,
|
|
2452
|
+
};
|
|
2227
2453
|
});
|
|
2228
2454
|
|
|
2229
2455
|
const changes = [];
|
|
2230
|
-
for (const {
|
|
2456
|
+
for (const {
|
|
2457
|
+
write,
|
|
2458
|
+
before,
|
|
2459
|
+
after,
|
|
2460
|
+
hookConflict,
|
|
2461
|
+
modeChanged,
|
|
2462
|
+
automaticRepairSuppressed,
|
|
2463
|
+
aheadUnverifiedVersions,
|
|
2464
|
+
} of prepared) {
|
|
2231
2465
|
const contentChanged = before !== after;
|
|
2232
|
-
const changed = !hookConflict && (contentChanged || modeChanged);
|
|
2233
|
-
const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor
|
|
2466
|
+
const changed = !automaticRepairSuppressed && !hookConflict && (contentChanged || modeChanged);
|
|
2467
|
+
const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor
|
|
2468
|
+
&& !hookConflict && !automaticRepairSuppressed);
|
|
2234
2469
|
changes.push({
|
|
2235
2470
|
path: write.path,
|
|
2236
2471
|
label: write.label,
|
|
2237
2472
|
changed,
|
|
2238
2473
|
applied: changed && writeApplied,
|
|
2239
|
-
already_present: !changed && !hookConflict,
|
|
2474
|
+
already_present: !changed && !hookConflict && !automaticRepairSuppressed,
|
|
2240
2475
|
hook_conflict: hookConflict,
|
|
2476
|
+
automatic_repair_suppressed: automaticRepairSuppressed,
|
|
2477
|
+
...(automaticRepairSuppressed ? {
|
|
2478
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
2479
|
+
exact_fix: mcpRegistryVerificationAction(aheadUnverifiedVersions),
|
|
2480
|
+
} : {}),
|
|
2241
2481
|
...(hookConflict ? { exact_fix: write.conflict_fix } : {}),
|
|
2242
2482
|
});
|
|
2243
2483
|
if (contentChanged && writeApplied) {
|
|
@@ -2303,7 +2543,7 @@ async function runSelfTest(options) {
|
|
|
2303
2543
|
'x-marrow-package-version': INSTALLER_ADAPTER_VERSION,
|
|
2304
2544
|
'x-marrow-install-version': INSTALLER_ADAPTER_VERSION,
|
|
2305
2545
|
'x-marrow-sdk-version': SDK_ADAPTER_VERSION,
|
|
2306
|
-
'x-marrow-mcp-version':
|
|
2546
|
+
'x-marrow-mcp-version': executableMcpTarget(options).version,
|
|
2307
2547
|
};
|
|
2308
2548
|
if (options.agentId) headers['x-marrow-agent-id'] = options.agentId;
|
|
2309
2549
|
|
|
@@ -2668,8 +2908,13 @@ function printReport(report) {
|
|
|
2668
2908
|
|
|
2669
2909
|
process.stdout.write('\nPlanned changes:\n');
|
|
2670
2910
|
for (const change of report.changes) {
|
|
2671
|
-
const marker = change.
|
|
2911
|
+
const marker = change.automatic_repair_suppressed
|
|
2912
|
+
? 'preserved unverified-ahead surface; repair suppressed'
|
|
2913
|
+
: change.applied ? 'wrote' : change.changed ? 'would write' : 'unchanged';
|
|
2672
2914
|
process.stdout.write(`- ${marker}: ${change.label} (${change.path})\n`);
|
|
2915
|
+
if (change.automatic_repair_suppressed && change.exact_fix) {
|
|
2916
|
+
process.stdout.write(` exact verification: ${change.exact_fix}\n`);
|
|
2917
|
+
}
|
|
2673
2918
|
}
|
|
2674
2919
|
|
|
2675
2920
|
process.stdout.write('\nSelf-test:\n');
|
|
@@ -2819,6 +3064,8 @@ function printReport(report) {
|
|
|
2819
3064
|
const processes = report.doctor.mcpProcesses;
|
|
2820
3065
|
process.stdout.write(`- MCP process versions: ${processes.active_versions.length ? processes.active_versions.join(', ') : processes.active_processes ? 'unknown' : 'none'}\n`);
|
|
2821
3066
|
process.stdout.write(`- stale/mixed/version-unknown MCP clients: ${processes.healthy ? 'no' : 'yes'}\n`);
|
|
3067
|
+
if (processes.ahead_unverified_versions.length) process.stdout.write(`- unverified-ahead MCP clients: ${processes.ahead_unverified_versions.join(', ')}\n`);
|
|
3068
|
+
if (processes.automatic_repair_suppressed) process.stdout.write('- automatic MCP process repair: suppressed pending official registry verification\n');
|
|
2822
3069
|
if (processes.restart_instruction) process.stdout.write(`- restart required: ${processes.restart_instruction}\n`);
|
|
2823
3070
|
if (processes.verification_command) process.stdout.write(`- verify repair: ${processes.verification_command}\n`);
|
|
2824
3071
|
}
|
|
@@ -2826,6 +3073,8 @@ function printReport(report) {
|
|
|
2826
3073
|
const configurations = report.doctor.mcpConfigurations;
|
|
2827
3074
|
process.stdout.write(`- configured MCP versions: ${configurations.configured_versions.length ? configurations.configured_versions.join(', ') : 'none pinned'}\n`);
|
|
2828
3075
|
process.stdout.write(`- stale/mixed/version-unknown MCP configuration: ${configurations.healthy ? 'no' : 'yes'}\n`);
|
|
3076
|
+
if (configurations.ahead_unverified_versions.length) process.stdout.write(`- unverified-ahead MCP configuration: ${configurations.ahead_unverified_versions.join(', ')}\n`);
|
|
3077
|
+
if (configurations.automatic_repair_suppressed) process.stdout.write('- automatic MCP configuration repair: suppressed pending official registry verification\n');
|
|
2829
3078
|
}
|
|
2830
3079
|
if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
|
|
2831
3080
|
process.stdout.write(`- live health: ${report.doctor.healthCommand}\n`);
|
|
@@ -2863,10 +3112,23 @@ async function install(options) {
|
|
|
2863
3112
|
const client = detectedClient(detection);
|
|
2864
3113
|
options.client = client;
|
|
2865
3114
|
options.agentId = String(options.agentId || '').trim() || stableAgentId(detection.root, client);
|
|
3115
|
+
const observedMcpProcesses = inspectMcpProcesses({ commands: options.processCommands });
|
|
3116
|
+
const observedMcpConfigurations = inspectMcpConfigurations(detection, { paths: options.mcpConfigPaths });
|
|
3117
|
+
const registryMetadata = await readMcpRegistryMetadata(options);
|
|
3118
|
+
const latestTargetOperation = Boolean(options.doctor || options.repair || options.update);
|
|
3119
|
+
const mcpTarget = resolveMcpTargetVersion({
|
|
3120
|
+
currentVersions: latestTargetOperation ? [
|
|
3121
|
+
...observedMcpProcesses.active_versions,
|
|
3122
|
+
...observedMcpConfigurations.configured_versions,
|
|
3123
|
+
] : [],
|
|
3124
|
+
registryMetadata: latestTargetOperation ? registryMetadata : null,
|
|
3125
|
+
});
|
|
3126
|
+
options.mcpTarget = mcpTarget;
|
|
3127
|
+
options.mcpTargetVersion = mcpTarget.version;
|
|
2866
3128
|
const plan = buildPlan(detection, options);
|
|
2867
3129
|
const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
|
|
2868
3130
|
const changes = applyPlan(plan, options);
|
|
2869
|
-
|
|
3131
|
+
let profile = activationProfile(detection, plan, changes, client);
|
|
2870
3132
|
options.activation = options.activate ? {
|
|
2871
3133
|
harness: client,
|
|
2872
3134
|
agent_id: options.agentId,
|
|
@@ -2897,8 +3159,33 @@ async function install(options) {
|
|
|
2897
3159
|
? repairConfigDiagnostics(configDiagnostics)
|
|
2898
3160
|
: [];
|
|
2899
3161
|
const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
|
|
2900
|
-
const mcpProcesses = inspectMcpProcesses({
|
|
2901
|
-
|
|
3162
|
+
const mcpProcesses = inspectMcpProcesses({
|
|
3163
|
+
commands: options.processCommands,
|
|
3164
|
+
expectedTarget: mcpTarget,
|
|
3165
|
+
});
|
|
3166
|
+
const mcpConfigurations = inspectMcpConfigurations(detection, {
|
|
3167
|
+
paths: options.mcpConfigPaths,
|
|
3168
|
+
expectedTarget: mcpTarget,
|
|
3169
|
+
});
|
|
3170
|
+
const aheadUnverifiedVersions = [...new Set([
|
|
3171
|
+
...mcpProcesses.ahead_unverified_versions,
|
|
3172
|
+
...mcpConfigurations.ahead_unverified_versions,
|
|
3173
|
+
...changes.flatMap((change) => change.ahead_unverified_versions || []),
|
|
3174
|
+
])].sort((left, right) => compareMcpVersions(left, right));
|
|
3175
|
+
const automaticMcpRepairSuppressed = aheadUnverifiedVersions.length > 0;
|
|
3176
|
+
const registryVerificationAction = automaticMcpRepairSuppressed
|
|
3177
|
+
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
3178
|
+
: null;
|
|
3179
|
+
if (automaticMcpRepairSuppressed) {
|
|
3180
|
+
profile = {
|
|
3181
|
+
...profile,
|
|
3182
|
+
configuration_complete: false,
|
|
3183
|
+
complete: false,
|
|
3184
|
+
automatic_repair_suppressed: true,
|
|
3185
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
3186
|
+
exact_fix: registryVerificationAction,
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
2902
3189
|
let selfTest;
|
|
2903
3190
|
try {
|
|
2904
3191
|
selfTest = await runSelfTest(options);
|
|
@@ -2912,6 +3199,12 @@ async function install(options) {
|
|
|
2912
3199
|
mcp_tool_profile: initialToolProfileReport(options.toolProfile),
|
|
2913
3200
|
};
|
|
2914
3201
|
}
|
|
3202
|
+
selfTest = alignMcpRecommendationVersions(
|
|
3203
|
+
selfTest,
|
|
3204
|
+
mcpTarget.version,
|
|
3205
|
+
'',
|
|
3206
|
+
registryVerificationAction,
|
|
3207
|
+
);
|
|
2915
3208
|
if (options.activate && !selfTest.activation_verified) {
|
|
2916
3209
|
throw new Error('Marrow activation failed: server confirmation was not returned');
|
|
2917
3210
|
}
|
|
@@ -2972,7 +3265,10 @@ async function install(options) {
|
|
|
2972
3265
|
attempted: true,
|
|
2973
3266
|
fixedConfig: changedConfig,
|
|
2974
3267
|
selfTestPassed,
|
|
2975
|
-
|
|
3268
|
+
automaticMcpRepairSuppressed,
|
|
3269
|
+
message: automaticMcpRepairSuppressed
|
|
3270
|
+
? registryVerificationAction
|
|
3271
|
+
: selfTestPassed
|
|
2976
3272
|
? selfTest.health === 'healthy'
|
|
2977
3273
|
? 'I fixed Marrow passive config, one-call runtime is active, and self-test passed.'
|
|
2978
3274
|
: `I fixed Marrow passive config and self-test passed; status is ${selfTest.health || 'unknown'}${selfTest.next_action ? `. Next action: ${selfTest.next_action}` : ''}.`
|
|
@@ -2984,7 +3280,7 @@ async function install(options) {
|
|
|
2984
3280
|
|
|
2985
3281
|
return {
|
|
2986
3282
|
root: detection.root,
|
|
2987
|
-
adapterProvenance:
|
|
3283
|
+
adapterProvenance: adapterProvenanceForMcpTarget(mcpTarget),
|
|
2988
3284
|
mode: plan.mode,
|
|
2989
3285
|
writeMode,
|
|
2990
3286
|
toolProfile: selfTest.mcp_tool_profile || initialToolProfileReport(options.toolProfile),
|
|
@@ -3020,7 +3316,10 @@ async function install(options) {
|
|
|
3020
3316
|
missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
|
|
3021
3317
|
mcpProcesses,
|
|
3022
3318
|
mcpConfigurations,
|
|
3023
|
-
|
|
3319
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
3320
|
+
automatic_mcp_repair_suppressed: automaticMcpRepairSuppressed,
|
|
3321
|
+
registry_verification_action: registryVerificationAction,
|
|
3322
|
+
recommendedFix: registryVerificationAction || mcpProcesses.exact_fix || mcpConfigurations.exact_fix || configDiagnostics.npm_token.recommended_fix || (!options.apiKey
|
|
3024
3323
|
? envHints.length
|
|
3025
3324
|
? `MARROW_API_KEY was found in a likely env file at ${envHints[0]}. Load that key from trusted secret storage, export only MARROW_API_KEY, then run npx @getmarrow/install --repair.`
|
|
3026
3325
|
: 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
|
|
@@ -3038,10 +3337,14 @@ async function install(options) {
|
|
|
3038
3337
|
...(options.keyFromArg
|
|
3039
3338
|
? ['Avoid --key in shared shells because command-line arguments can be visible in process listings. Prefer MARROW_API_KEY in your environment or secret manager.']
|
|
3040
3339
|
: []),
|
|
3041
|
-
...(
|
|
3340
|
+
...(mcpProcesses.ahead_unverified
|
|
3341
|
+
? ['Registry-unverified ahead Marrow MCP clients are active. Automatic repair is suppressed; run the exact registry verification action before owner-directed replacement.']
|
|
3342
|
+
: !mcpProcesses.healthy
|
|
3042
3343
|
? ['Stale, mixed, or version-unknown Marrow MCP clients are active. Run the exact repair command, then restart every owning harness.']
|
|
3043
3344
|
: []),
|
|
3044
|
-
...(
|
|
3345
|
+
...(mcpConfigurations.ahead_unverified || changes.some((change) => change.automatic_repair_suppressed)
|
|
3346
|
+
? ['Registry-unverified ahead Marrow MCP configuration was preserved. It was not copied into other managed targets, and automatic repair is suppressed for that surface.']
|
|
3347
|
+
: !mcpConfigurations.healthy
|
|
3045
3348
|
? ['Stale, mixed, or version-unknown Marrow MCP versions remain in owner configuration. Repair each owning workspace and restart its harness.']
|
|
3046
3349
|
: []),
|
|
3047
3350
|
],
|
|
@@ -3093,6 +3396,7 @@ module.exports = {
|
|
|
3093
3396
|
GROK_NATIVE_HOOK_MATCHER,
|
|
3094
3397
|
printReport,
|
|
3095
3398
|
buildMcpToolProfileReport,
|
|
3399
|
+
resolveMcpTargetVersion,
|
|
3096
3400
|
resolveToolProfile,
|
|
3097
3401
|
PRIMARY_TOOL_NAMES,
|
|
3098
3402
|
ADAPTER_PROVENANCE,
|