@getmarrow/install 0.1.40 → 0.1.41
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 +12 -2
- package/package.json +1 -1
- package/src/installer.js +132 -7
package/README.md
CHANGED
|
@@ -91,7 +91,17 @@ npx @getmarrow/install controller stop
|
|
|
91
91
|
|
|
92
92
|
Persistent controller lifecycle is currently Linux-only. On macOS or Windows, activation still installs and verifies the supported hooks without starting or signaling a background process; 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.
|
|
93
93
|
|
|
94
|
-
## What's New in v0.1.
|
|
94
|
+
## What's New in v0.1.41
|
|
95
|
+
|
|
96
|
+
v0.1.41 makes stale client detection and recovery part of normal installation health:
|
|
97
|
+
|
|
98
|
+
- `doctor` detects active stale, mixed, or version-unknown Marrow MCP processes, including direct `node_modules/.bin/marrow-mcp` launches, without exposing command lines or credentials;
|
|
99
|
+
- when repair is needed, `doctor` reports the executable pinned setup command, the separate owning-harness restart requirement, and the executable verification command; it does not terminate harness processes itself;
|
|
100
|
+
- certified hooks pin MCP `3.9.57` and SDK `3.7.56` so the installed runtime matches the advertised control contract;
|
|
101
|
+
- existing harnesses retain their honest coverage level: native hooks where supported, MCP calls where available, and governed wrappers or event contracts elsewhere;
|
|
102
|
+
- package upgrades remain operator-approved and never rotate keys or rewrite unrelated configuration.
|
|
103
|
+
|
|
104
|
+
## Previous: v0.1.40
|
|
95
105
|
|
|
96
106
|
v0.1.40 binds governed runs to a privacy-safe workspace fingerprint and separates observed execution from verified completion:
|
|
97
107
|
|
|
@@ -99,7 +109,7 @@ v0.1.40 binds governed runs to a privacy-safe workspace fingerprint and separate
|
|
|
99
109
|
- passive prompt telemetry is buffered locally rather than delaying the agent turn;
|
|
100
110
|
- transient read failures can use clearly labeled owner-only last-known guidance, while authentication failures never use cache;
|
|
101
111
|
- `doctor` prints the exact `npx -y @getmarrow/mcp@latest ping` command for measured current/p50/p99 latency, last success, and backlog health;
|
|
102
|
-
- certified hook commands pin MCP `3.9.56` and SDK `3.7.55` so advertised behavior matches
|
|
112
|
+
- certified hook commands pin MCP `3.9.56` and SDK `3.7.55` so advertised behavior matches that release's deployed server contract;
|
|
103
113
|
- governed runtime requests attach a stable privacy-safe project fingerprint and harness label without sending the raw working-directory path;
|
|
104
114
|
- successful command exit remains observed execution, not verified business completion, unless a verification command or explicit proof file supplies evidence;
|
|
105
115
|
- the integration matrix now reports prompt injection, pre-action, action result, closure, proof, cached brief, restart survival, evidence adapter, and safe repair separately.
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -8,9 +8,9 @@ const { controllerStatus, controllerSupportedPlatform, ensureGovernanceControlle
|
|
|
8
8
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
9
9
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
10
10
|
const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
|
|
11
|
-
const MCP_ADAPTER_VERSION = '3.9.
|
|
12
|
-
const SDK_ADAPTER_VERSION = '3.7.
|
|
13
|
-
const SDK_ADAPTER_INTEGRITY = 'sha512-
|
|
11
|
+
const MCP_ADAPTER_VERSION = '3.9.57';
|
|
12
|
+
const SDK_ADAPTER_VERSION = '3.7.56';
|
|
13
|
+
const SDK_ADAPTER_INTEGRITY = 'sha512-5htliY4wfn8a1mbLT9N4OWXqhp9fWzMHuAQgUetc3RUKjOes5mWB3t91/leRKFRRIgCnEczJN6jHXg7Aw489Mw==';
|
|
14
14
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
15
15
|
const MCP_PACKAGE_SPEC = `@getmarrow/mcp@${MCP_ADAPTER_VERSION}`;
|
|
16
16
|
const MCP_CONTEXT_HOOK_COMMAND = `npx -y ${MCP_PACKAGE_SPEC} context-hook`;
|
|
@@ -42,6 +42,116 @@ const HARNESS_CAPABILITY_REGISTRY = Object.freeze([
|
|
|
42
42
|
{ client: 'custom', capability_level: 'event_contract', automatic: [], install_surface: 'event_contract' },
|
|
43
43
|
]);
|
|
44
44
|
|
|
45
|
+
function explicitMcpVersion(command) {
|
|
46
|
+
const match = String(command || '').match(/@getmarrow\/mcp@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
|
|
47
|
+
return match ? match[1] : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readMcpPackageVersion(packageRoot) {
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
53
|
+
return typeof pkg.version === 'string' && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pkg.version)
|
|
54
|
+
? pkg.version
|
|
55
|
+
: null;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function packageMcpVersion(command) {
|
|
62
|
+
const normalized = String(command || '').replace(/\0/g, ' ');
|
|
63
|
+
const packageRoots = [];
|
|
64
|
+
for (const match of normalized.matchAll(/(\/[^\s]+\/node_modules\/@getmarrow\/mcp)(?:\/|\s|$)/g)) {
|
|
65
|
+
packageRoots.push(match[1]);
|
|
66
|
+
}
|
|
67
|
+
for (const match of normalized.matchAll(/(\/[^\s]+\/node_modules\/\.bin\/marrow-mcp)(?:\s|$)/g)) {
|
|
68
|
+
const binPath = match[1];
|
|
69
|
+
packageRoots.push(path.resolve(path.dirname(binPath), '..', '@getmarrow', 'mcp'));
|
|
70
|
+
try {
|
|
71
|
+
const resolved = fs.realpathSync(binPath);
|
|
72
|
+
const marker = `${path.sep}node_modules${path.sep}@getmarrow${path.sep}mcp${path.sep}`;
|
|
73
|
+
const markerIndex = resolved.indexOf(marker);
|
|
74
|
+
if (markerIndex >= 0) packageRoots.push(resolved.slice(0, markerIndex + marker.length - 1));
|
|
75
|
+
} catch {
|
|
76
|
+
// The derived package root still gives a deterministic best-effort lookup.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const packageRoot of [...new Set(packageRoots)]) {
|
|
80
|
+
const version = readMcpPackageVersion(packageRoot);
|
|
81
|
+
if (version) return version;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isMcpProcessCommand(command) {
|
|
87
|
+
const raw = String(command || '');
|
|
88
|
+
const args = (raw.includes('\0') ? raw.split('\0') : raw.trim().split(/\s+/)).filter(Boolean);
|
|
89
|
+
if (!args.length) return false;
|
|
90
|
+
|
|
91
|
+
const executable = path.basename(args[0]);
|
|
92
|
+
if (new Set(['bash', 'bwrap', 'dash', 'fish', 'sh', 'zsh']).has(executable)) return false;
|
|
93
|
+
if (executable === 'marrow-mcp') return true;
|
|
94
|
+
|
|
95
|
+
if (executable === 'node'
|
|
96
|
+
&& args[1]
|
|
97
|
+
&& /(?:^|\/)node_modules\/(?:@getmarrow\/mcp(?:\/|$)|\.bin\/marrow-mcp$)/.test(args[1])) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const packageManagers = new Set(['bun', 'bunx', 'npm', 'npm-cli.js', 'npx', 'npx-cli.js', 'pnpm', 'pnpx', 'yarn']);
|
|
102
|
+
const runner = executable === 'node' && args[1] ? path.basename(args[1]) : executable;
|
|
103
|
+
return packageManagers.has(runner)
|
|
104
|
+
&& args.some((arg) => /^@getmarrow\/mcp(?:@[^\s]+)?$/.test(arg));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readLinuxProcessCommands(procRoot = '/proc') {
|
|
108
|
+
if (process.platform !== 'linux') return [];
|
|
109
|
+
try {
|
|
110
|
+
return fs.readdirSync(procRoot, { withFileTypes: true })
|
|
111
|
+
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
|
|
112
|
+
.map((entry) => {
|
|
113
|
+
try {
|
|
114
|
+
return fs.readFileSync(path.join(procRoot, entry.name, 'cmdline'), 'utf8');
|
|
115
|
+
} catch {
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function inspectMcpProcesses(options = {}) {
|
|
126
|
+
const commands = Array.isArray(options.commands)
|
|
127
|
+
? options.commands.map(String)
|
|
128
|
+
: readLinuxProcessCommands(options.procRoot);
|
|
129
|
+
const active = commands
|
|
130
|
+
.filter(isMcpProcessCommand)
|
|
131
|
+
.map((command) => explicitMcpVersion(command) || packageMcpVersion(command) || 'unknown');
|
|
132
|
+
const versions = [...new Set(active.filter((version) => version !== 'unknown'))].sort();
|
|
133
|
+
const unknownVersionProcesses = active.filter((version) => version === 'unknown').length;
|
|
134
|
+
const staleVersions = versions.filter((version) => version !== MCP_ADAPTER_VERSION);
|
|
135
|
+
const mixedVersions = versions.length > 1 || (versions.length > 0 && unknownVersionProcesses > 0);
|
|
136
|
+
const stale = staleVersions.length > 0;
|
|
137
|
+
const needsRepair = stale || mixedVersions || unknownVersionProcesses > 0;
|
|
138
|
+
const repairCommand = `npx -y @getmarrow/mcp@${MCP_ADAPTER_VERSION} setup`;
|
|
139
|
+
return {
|
|
140
|
+
available: process.platform === 'linux' || Array.isArray(options.commands),
|
|
141
|
+
expected_version: MCP_ADAPTER_VERSION,
|
|
142
|
+
active_processes: active.length,
|
|
143
|
+
active_versions: versions,
|
|
144
|
+
unknown_version_processes: unknownVersionProcesses,
|
|
145
|
+
stale_versions: staleVersions,
|
|
146
|
+
mixed_versions: mixedVersions,
|
|
147
|
+
healthy: !needsRepair,
|
|
148
|
+
exact_fix: needsRepair ? repairCommand : null,
|
|
149
|
+
restart_required: needsRepair,
|
|
150
|
+
restart_instruction: needsRepair ? 'Restart every owning harness to replace its active Marrow MCP process.' : null,
|
|
151
|
+
verification_command: needsRepair ? 'npx -y @getmarrow/install@latest doctor' : null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
45
155
|
function sourceClient() {
|
|
46
156
|
const raw = String(process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || process.env.MARROW_AGENT_CLIENT || '').trim().toLowerCase().replace(/\s+/g, '-').replace(/^@/, '');
|
|
47
157
|
const aliases = {
|
|
@@ -1530,6 +1640,13 @@ function printReport(report) {
|
|
|
1530
1640
|
process.stdout.write(`- missing env: ${report.doctor.missingEnv.length ? report.doctor.missingEnv.join(', ') : 'none'}\n`);
|
|
1531
1641
|
if (report.doctor.envHints.length) process.stdout.write(`- possible env files: ${report.doctor.envHints.join(', ')}\n`);
|
|
1532
1642
|
process.stdout.write(`- missing hooks/config: ${report.doctor.missingHooks.length ? report.doctor.missingHooks.join('; ') : 'none'}\n`);
|
|
1643
|
+
if (report.doctor.mcpProcesses?.available) {
|
|
1644
|
+
const processes = report.doctor.mcpProcesses;
|
|
1645
|
+
process.stdout.write(`- MCP process versions: ${processes.active_versions.length ? processes.active_versions.join(', ') : processes.active_processes ? 'unknown' : 'none'}\n`);
|
|
1646
|
+
process.stdout.write(`- stale/mixed/version-unknown MCP clients: ${processes.healthy ? 'no' : 'yes'}\n`);
|
|
1647
|
+
if (processes.restart_instruction) process.stdout.write(`- restart required: ${processes.restart_instruction}\n`);
|
|
1648
|
+
if (processes.verification_command) process.stdout.write(`- verify repair: ${processes.verification_command}\n`);
|
|
1649
|
+
}
|
|
1533
1650
|
if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
|
|
1534
1651
|
process.stdout.write(`- live health: ${report.doctor.healthCommand}\n`);
|
|
1535
1652
|
}
|
|
@@ -1590,6 +1707,7 @@ async function install(options) {
|
|
|
1590
1707
|
? repairConfigDiagnostics(configDiagnostics)
|
|
1591
1708
|
: [];
|
|
1592
1709
|
const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
|
|
1710
|
+
const mcpProcesses = inspectMcpProcesses({ commands: options.processCommands });
|
|
1593
1711
|
let selfTest;
|
|
1594
1712
|
try {
|
|
1595
1713
|
selfTest = await runSelfTest(options);
|
|
@@ -1681,7 +1799,8 @@ async function install(options) {
|
|
|
1681
1799
|
missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
|
|
1682
1800
|
envHints,
|
|
1683
1801
|
missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
|
|
1684
|
-
|
|
1802
|
+
mcpProcesses,
|
|
1803
|
+
recommendedFix: mcpProcesses.exact_fix || configDiagnostics.npm_token.recommended_fix || (!options.apiKey
|
|
1685
1804
|
? envHints.length
|
|
1686
1805
|
? `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.`
|
|
1687
1806
|
: 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
|
|
@@ -1694,9 +1813,14 @@ async function install(options) {
|
|
|
1694
1813
|
sdkDependency,
|
|
1695
1814
|
controller,
|
|
1696
1815
|
selfTest,
|
|
1697
|
-
warnings:
|
|
1698
|
-
|
|
1699
|
-
|
|
1816
|
+
warnings: [
|
|
1817
|
+
...(options.keyFromArg
|
|
1818
|
+
? ['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.']
|
|
1819
|
+
: []),
|
|
1820
|
+
...(!mcpProcesses.healthy
|
|
1821
|
+
? ['Stale, mixed, or version-unknown Marrow MCP clients are active. Run the exact repair command, then restart every owning harness.']
|
|
1822
|
+
: []),
|
|
1823
|
+
],
|
|
1700
1824
|
};
|
|
1701
1825
|
}
|
|
1702
1826
|
|
|
@@ -1725,6 +1849,7 @@ module.exports = {
|
|
|
1725
1849
|
passiveRuntimeSource,
|
|
1726
1850
|
inspectNpmTokenConfig,
|
|
1727
1851
|
inspectSdkDependency,
|
|
1852
|
+
inspectMcpProcesses,
|
|
1728
1853
|
buildInstallValueMoment,
|
|
1729
1854
|
buildTokenValueProof,
|
|
1730
1855
|
stableAgentId,
|